From 86f88ea590888b507f6883e2e1a7a268ec9d12c5 Mon Sep 17 00:00:00 2001 From: YunaiV Date: Sat, 17 May 2025 09:48:32 +0800 Subject: [PATCH] reactor:移除 pay 的 api 包 --- pom.xml | 2 +- yudao-module-pay/pom.xml | 75 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----- yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/api/notify/dto/PayOrderNotifyReqDTO.java | 34 ++++++++++++++++++++++++++++++++++ yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/api/notify/dto/PayRefundNotifyReqDTO.java | 40 ++++++++++++++++++++++++++++++++++++++++ yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/api/notify/dto/PayTransferNotifyReqDTO.java | 34 ++++++++++++++++++++++++++++++++++ yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/api/notify/package-info.java | 4 ++++ yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/api/order/PayOrderApi.java | 40 ++++++++++++++++++++++++++++++++++++++++ yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/api/order/PayOrderApiImpl.java | 39 +++++++++++++++++++++++++++++++++++++++ yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/api/order/dto/PayOrderCreateReqDTO.java | 65 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/api/order/dto/PayOrderRespDTO.java | 68 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/api/package-info.java | 4 ++++ yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/api/refund/PayRefundApi.java | 31 +++++++++++++++++++++++++++++++ yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/api/refund/PayRefundApiImpl.java | 35 +++++++++++++++++++++++++++++++++++ yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/api/refund/dto/PayRefundCreateReqDTO.java | 57 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/api/refund/dto/PayRefundRespDTO.java | 65 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/api/transfer/PayTransferApi.java | 31 +++++++++++++++++++++++++++++++ yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/api/transfer/PayTransferApiImpl.java | 49 +++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/api/transfer/dto/PayTransferCreateReqDTO.java | 120 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/api/transfer/dto/PayTransferCreateRespDTO.java | 28 ++++++++++++++++++++++++++++ yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/api/transfer/dto/PayTransferRespDTO.java | 81 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/api/wallet/PayWalletApi.java | 29 +++++++++++++++++++++++++++++ yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/api/wallet/PayWalletApiImpl.java | 41 +++++++++++++++++++++++++++++++++++++++++ yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/api/wallet/dto/PayWalletAddBalanceReqDTO.java | 49 +++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/api/wallet/dto/PayWalletRespDTO.java | 52 ++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/app/PayAppController.java | 108 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/app/vo/PayAppBaseVO.java | 47 +++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/app/vo/PayAppCreateReqVO.java | 14 ++++++++++++++ yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/app/vo/PayAppPageItemRespVO.java | 26 ++++++++++++++++++++++++++ yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/app/vo/PayAppPageReqVO.java | 33 +++++++++++++++++++++++++++++++++ yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/app/vo/PayAppRespVO.java | 25 +++++++++++++++++++++++++ yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/app/vo/PayAppUpdateReqVO.java | 17 +++++++++++++++++ yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/app/vo/PayAppUpdateStatusReqVO.java | 20 ++++++++++++++++++++ yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/channel/PayChannelController.java | 82 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/channel/vo/PayChannelBaseVO.java | 31 +++++++++++++++++++++++++++++++ yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/channel/vo/PayChannelCreateReqVO.java | 25 +++++++++++++++++++++++++ yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/channel/vo/PayChannelRespVO.java | 25 +++++++++++++++++++++++++ yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/channel/vo/PayChannelUpdateReqVO.java | 20 ++++++++++++++++++++ yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/demo/PayDemoOrderController.java | 76 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/demo/PayDemoWithdrawController.http | 50 ++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/demo/PayDemoWithdrawController.java | 63 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/demo/vo/order/PayDemoOrderCreateReqVO.java | 16 ++++++++++++++++ yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/demo/vo/order/PayDemoOrderRespVO.java | 54 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/demo/vo/withdraw/PayDemoWithdrawCreateReqVO.java | 42 ++++++++++++++++++++++++++++++++++++++++++ yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/demo/vo/withdraw/PayDemoWithdrawRespVO.java | 47 +++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/notify/PayNotifyController.java | 169 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/notify/vo/PayNotifyTaskDetailRespVO.java | 42 ++++++++++++++++++++++++++++++++++++++++++ yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/notify/vo/PayNotifyTaskPageReqVO.java | 45 +++++++++++++++++++++++++++++++++++++++++++++ yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/notify/vo/PayNotifyTaskRespVO.java | 56 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/order/PayOrderController.java | 145 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/order/vo/PayOrderBaseVO.java | 89 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/order/vo/PayOrderDetailsRespVO.java | 45 +++++++++++++++++++++++++++++++++++++++++++++ yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/order/vo/PayOrderExcelVO.java | 67 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/order/vo/PayOrderExportReqVO.java | 37 +++++++++++++++++++++++++++++++++++++ yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/order/vo/PayOrderPageItemRespVO.java | 25 +++++++++++++++++++++++++ yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/order/vo/PayOrderPageReqVO.java | 42 ++++++++++++++++++++++++++++++++++++++++++ yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/order/vo/PayOrderRespVO.java | 22 ++++++++++++++++++++++ yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/order/vo/PayOrderSubmitReqVO.java | 33 +++++++++++++++++++++++++++++++++ yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/order/vo/PayOrderSubmitRespVO.java | 18 ++++++++++++++++++ yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/refund/PayRefundController.java | 90 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/refund/vo/PayRefundBaseVO.java | 78 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/refund/vo/PayRefundDetailsRespVO.java | 40 ++++++++++++++++++++++++++++++++++++++++ yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/refund/vo/PayRefundExcelVO.java | 61 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/refund/vo/PayRefundExportReqVO.java | 40 ++++++++++++++++++++++++++++++++++++++++ yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/refund/vo/PayRefundPageItemRespVO.java | 25 +++++++++++++++++++++++++ yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/refund/vo/PayRefundPageReqVO.java | 45 +++++++++++++++++++++++++++++++++++++++++++++ yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/transfer/PayTransferController.java | 91 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/transfer/vo/PayTransferCreateRespVO.java | 16 ++++++++++++++++ yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/transfer/vo/PayTransferPageReqVO.java | 45 +++++++++++++++++++++++++++++++++++++++++++++ yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/transfer/vo/PayTransferRespVO.java | 100 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/wallet/PayWalletController.java | 70 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/wallet/PayWalletRechargeController.java | 59 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/wallet/PayWalletRechargePackageController.java | 75 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/wallet/PayWalletTransactionController.java | 43 +++++++++++++++++++++++++++++++++++++++++++ yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/wallet/vo/rechargepackage/WalletRechargePackageBaseVO.java | 31 +++++++++++++++++++++++++++++++ yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/wallet/vo/rechargepackage/WalletRechargePackageCreateReqVO.java | 14 ++++++++++++++ yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/wallet/vo/rechargepackage/WalletRechargePackagePageReqVO.java | 30 ++++++++++++++++++++++++++++++ yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/wallet/vo/rechargepackage/WalletRechargePackageRespVO.java | 19 +++++++++++++++++++ yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/wallet/vo/rechargepackage/WalletRechargePackageUpdateReqVO.java | 20 ++++++++++++++++++++ yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/wallet/vo/transaction/PayWalletTransactionPageReqVO.java | 23 +++++++++++++++++++++++ yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/wallet/vo/transaction/PayWalletTransactionRespVO.java | 35 +++++++++++++++++++++++++++++++++++ yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/wallet/vo/wallet/PayWalletBaseVO.java | 39 +++++++++++++++++++++++++++++++++++++++ yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/wallet/vo/wallet/PayWalletPageReqVO.java | 33 +++++++++++++++++++++++++++++++++ yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/wallet/vo/wallet/PayWalletRespVO.java | 22 ++++++++++++++++++++++ yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/wallet/vo/wallet/PayWalletUpdateBalanceReqVO.java | 19 +++++++++++++++++++ yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/wallet/vo/wallet/PayWalletUserReqVO.java | 16 ++++++++++++++++ yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/app/channel/AppPayChannelController.java | 39 +++++++++++++++++++++++++++++++++++++++ yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/app/order/AppPayOrderController.http | 63 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/app/order/AppPayOrderController.java | 81 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/app/order/vo/AppPayOrderSubmitReqVO.java | 15 +++++++++++++++ yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/app/order/vo/AppPayOrderSubmitRespVO.java | 15 +++++++++++++++ yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/app/transfer/AppPayTransferController.java | 36 ++++++++++++++++++++++++++++++++++++ yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/app/wallet/AppPayWalletController.java | 42 ++++++++++++++++++++++++++++++++++++++++++ yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/app/wallet/AppPayWalletRechargeController.java | 72 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/app/wallet/AppPayWalletRechargePackageController.java | 42 ++++++++++++++++++++++++++++++++++++++++++ yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/app/wallet/AppPayWalletTransactionController.java | 61 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/app/wallet/vo/recharge/AppPayWalletPackageRespVO.java | 20 ++++++++++++++++++++ yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/app/wallet/vo/recharge/AppPayWalletRechargeCreateReqVO.java | 26 ++++++++++++++++++++++++++ yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/app/wallet/vo/recharge/AppPayWalletRechargeCreateRespVO.java | 16 ++++++++++++++++ yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/app/wallet/vo/recharge/AppPayWalletRechargeRespVO.java | 42 ++++++++++++++++++++++++++++++++++++++++++ yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/app/wallet/vo/transaction/AppPayWalletTransactionPageReqVO.java | 31 +++++++++++++++++++++++++++++++ yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/app/wallet/vo/transaction/AppPayWalletTransactionRespVO.java | 24 ++++++++++++++++++++++++ yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/app/wallet/vo/transaction/AppPayWalletTransactionSummaryRespVO.java | 16 ++++++++++++++++ yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/app/wallet/vo/wallet/AppPayWalletRespVO.java | 19 +++++++++++++++++++ yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/package-info.java | 6 ++++++ yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/convert/app/PayAppConvert.java | 49 +++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/convert/channel/PayChannelConvert.java | 28 ++++++++++++++++++++++++++++ yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/convert/order/PayOrderConvert.java | 74 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/convert/package-info.java | 6 ++++++ yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/convert/refund/PayRefundConvert.java | 53 +++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/convert/wallet/PayWalletConvert.java | 21 +++++++++++++++++++++ yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/convert/wallet/PayWalletRechargeConvert.java | 43 +++++++++++++++++++++++++++++++++++++++++++ yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/convert/wallet/PayWalletRechargePackageConvert.java | 29 +++++++++++++++++++++++++++++ yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/convert/wallet/PayWalletTransactionConvert.java | 20 ++++++++++++++++++++ yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/convert/《芋道 Spring Boot 对象转换 MapStruct 入门》.md | 1 + yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/dal/dataobject/app/PayAppDO.java | 66 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/dal/dataobject/channel/PayChannelDO.java | 118 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/dal/dataobject/demo/PayDemoOrderDO.java | 87 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/dal/dataobject/demo/PayDemoWithdrawDO.java | 84 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/dal/dataobject/notify/PayNotifyLogDO.java | 51 +++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/dal/dataobject/notify/PayNotifyTaskDO.java | 106 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/dal/dataobject/order/PayOrderDO.java | 138 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/dal/dataobject/order/PayOrderExtensionDO.java | 96 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/dal/dataobject/refund/PayRefundDO.java | 160 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/dal/dataobject/transfer/PayTransferDO.java | 146 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/dal/dataobject/wallet/PayWalletDO.java | 59 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/dal/dataobject/wallet/PayWalletRechargeDO.java | 116 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/dal/dataobject/wallet/PayWalletRechargePackageDO.java | 47 +++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/dal/dataobject/wallet/PayWalletTransactionDO.java | 66 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/app/PayAppMapper.java | 26 ++++++++++++++++++++++++++ yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/channel/PayChannelMapper.java | 31 +++++++++++++++++++++++++++++++ yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/demo/PayDemoOrderMapper.java | 28 ++++++++++++++++++++++++++++ yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/demo/PayDemoWithdrawMapper.java | 24 ++++++++++++++++++++++++ yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/notify/PayNotifyLogMapper.java | 16 ++++++++++++++++ yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/notify/PayNotifyTaskMapper.java | 46 ++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/order/PayOrderExtensionMapper.java | 38 ++++++++++++++++++++++++++++++++++++++ yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/order/PayOrderMapper.java | 62 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/refund/PayRefundMapper.java | 78 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/transfer/PayTransferMapper.java | 65 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/wallet/PayWalletMapper.java | 134 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/wallet/PayWalletRechargeMapper.java | 31 +++++++++++++++++++++++++++++++ yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/wallet/PayWalletRechargePackageMapper.java | 32 ++++++++++++++++++++++++++++++++ yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/wallet/PayWalletTransactionMapper.java | 67 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/dal/redis/RedisKeyConstants.java | 36 ++++++++++++++++++++++++++++++++++++ yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/dal/redis/no/PayNoRedisDAO.java | 39 +++++++++++++++++++++++++++++++++++++++ yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/dal/redis/notify/PayNotifyLockRedisDAO.java | 39 +++++++++++++++++++++++++++++++++++++++ yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/dal/redis/wallet/PayWalletLockRedisDAO.java | 42 ++++++++++++++++++++++++++++++++++++++++++ yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/enums/DictTypeConstants.java | 20 ++++++++++++++++++++ yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/enums/ErrorCodeConstants.java | 97 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/enums/MessageTemplateConstants.java | 14 ++++++++++++++ yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/enums/PayChannelEnum.java | 67 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/enums/demo/PayDemoWithdrawStatusEnum.java | 42 ++++++++++++++++++++++++++++++++++++++++++ yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/enums/demo/PayDemoWithdrawTypeEnum.java | 39 +++++++++++++++++++++++++++++++++++++++ yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/enums/notify/PayNotifyStatusEnum.java | 32 ++++++++++++++++++++++++++++++++ yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/enums/notify/PayNotifyTypeEnum.java | 29 +++++++++++++++++++++++++++++ yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/enums/order/PayOrderStatusEnum.java | 84 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/enums/refund/PayRefundStatusEnum.java | 32 ++++++++++++++++++++++++++++++++ yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/enums/transfer/PayTransferStatusEnum.java | 67 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/enums/wallet/PayWalletBizTypeEnum.java | 45 +++++++++++++++++++++++++++++++++++++++++++++ yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/framework/job/config/PayJobConfiguration.java | 28 ++++++++++++++++++++++++++++ yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/framework/job/core/package-info.java | 4 ++++ yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/framework/package-info.java | 6 ++++++ yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/config/PayConfiguration.java | 18 ++++++++++++++++++ yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/config/PayProperties.java | 69 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/PayClient.java | 118 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/PayClientConfig.java | 27 +++++++++++++++++++++++++++ yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/PayClientFactory.java | 28 ++++++++++++++++++++++++++++ yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/dto/order/PayOrderRespDTO.java | 141 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/dto/order/PayOrderUnifiedReqDTO.java | 92 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/dto/refund/PayRefundRespDTO.java | 115 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/dto/refund/PayRefundUnifiedReqDTO.java | 70 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/dto/transfer/PayTransferRespDTO.java | 116 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/dto/transfer/PayTransferUnifiedReqDTO.java | 73 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/exception/PayClientException.java | 17 +++++++++++++++++ yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/impl/AbstractPayClient.java | 251 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/impl/NonePayClientConfig.java | 31 +++++++++++++++++++++++++++++++ yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/impl/PayClientFactoryImpl.java | 97 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/impl/alipay/AbstractAlipayPayClient.java | 379 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/impl/alipay/AlipayAppPayClient.java | 60 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/impl/alipay/AlipayBarPayClient.java | 86 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/impl/alipay/AlipayPayClientConfig.java | 128 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/impl/alipay/AlipayPcPayClient.java | 70 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/impl/alipay/AlipayQrPayClient.java | 66 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/impl/alipay/AlipayWapPayClient.java | 59 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/impl/mock/MockPayClient.java | 84 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/impl/wallet/WalletPayClient.java | 251 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/impl/weixin/AbstractWxPayClient.java | 597 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/impl/weixin/WxAppPayClient.java | 63 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/impl/weixin/WxBarPayClient.java | 107 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/impl/weixin/WxLitePayClient.java | 22 ++++++++++++++++++++++ yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/impl/weixin/WxNativePayClient.java | 59 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/impl/weixin/WxPayClientConfig.java | 107 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/impl/weixin/WxPubPayClient.java | 81 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/impl/weixin/WxWapPayClient.java | 62 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/enums/PayOrderDisplayModeEnum.java | 29 +++++++++++++++++++++++++++++ yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/package-info.java | 6 ++++++ yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/framework/web/config/PayWebConfiguration.java | 24 ++++++++++++++++++++++++ yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/framework/web/package-info.java | 4 ++++ yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/job/notify/PayNotifyJob.java | 31 +++++++++++++++++++++++++++++++ yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/job/order/PayOrderExpireJob.java | 31 +++++++++++++++++++++++++++++++ yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/job/order/PayOrderSyncJob.java | 43 +++++++++++++++++++++++++++++++++++++++++++ yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/job/refund/PayRefundSyncJob.java | 31 +++++++++++++++++++++++++++++++ yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/job/transfer/PayTransferSyncJob.java | 30 ++++++++++++++++++++++++++++++ yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/package-info.java | 10 ++++++++++ yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/service/app/PayAppService.java | 115 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/service/app/PayAppServiceImpl.java | 167 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/service/channel/PayChannelService.java | 104 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/service/channel/PayChannelServiceImpl.java | 165 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/service/demo/PayDemoOrderService.java | 67 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/service/demo/PayDemoOrderServiceImpl.java | 265 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/service/demo/PayDemoWithdrawService.java | 48 ++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/service/demo/PayDemoWithdrawServiceImpl.java | 195 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/service/notify/PayNotifyService.java | 57 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/service/notify/PayNotifyServiceImpl.java | 323 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/service/order/PayOrderService.java | 159 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/service/order/PayOrderServiceImpl.java | 605 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/service/refund/PayRefundService.java | 82 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/service/refund/PayRefundServiceImpl.java | 331 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/service/transfer/PayTransferService.java | 70 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/service/transfer/PayTransferServiceImpl.java | 319 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/service/wallet/PayWalletRechargePackageService.java | 71 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/service/wallet/PayWalletRechargePackageServiceImpl.java | 113 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/service/wallet/PayWalletRechargeService.java | 64 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/service/wallet/PayWalletRechargeServiceImpl.java | 331 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/service/wallet/PayWalletService.java | 99 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/service/wallet/PayWalletServiceImpl.java | 233 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/service/wallet/PayWalletTransactionService.java | 74 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/service/wallet/PayWalletTransactionServiceImpl.java | 95 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/service/wallet/bo/WalletTransactionCreateReqBO.java | 59 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-pay/src/test/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/impl/PayClientFactoryImplIntegrationTest.java | 134 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-pay/src/test/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/impl/alipay/AbstractAlipayClientTest.java | 221 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-pay/src/test/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/impl/alipay/AlipayBarPayClientTest.java | 170 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-pay/src/test/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/impl/alipay/AlipayPcPayClientTest.java | 131 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-pay/src/test/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/impl/alipay/AlipayQrPayClientTest.java | 147 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-pay/src/test/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/impl/alipay/AlipayWapPayClientTest.java | 111 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-pay/src/test/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/impl/weixin/WxBarPayClientIntegrationTest.java | 123 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-pay/src/test/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/impl/weixin/WxNativePayClientIntegrationTest.java | 84 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-pay/src/test/java/cn/iocoder/yudao/module/pay/service/app/PayAppServiceTest.java | 260 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-pay/src/test/java/cn/iocoder/yudao/module/pay/service/channel/PayChannelServiceTest.java | 337 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-pay/src/test/java/cn/iocoder/yudao/module/pay/service/notify/PayNotifyServiceTest.java | 353 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-pay/src/test/java/cn/iocoder/yudao/module/pay/service/order/PayOrderServiceTest.java | 1104 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-pay/src/test/java/cn/iocoder/yudao/module/pay/service/refund/PayRefundServiceTest.java | 699 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-pay/src/test/resources/application-unit-test.yaml | 48 ++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-pay/src/test/resources/logback.xml | 4 ++++ yudao-module-pay/src/test/resources/sql/clean.sql | 7 +++++++ yudao-module-pay/src/test/resources/sql/create_tables.sql | 147 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-pay/yudao-module-pay-api/pom.xml | 33 --------------------------------- yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/api/notify/dto/PayOrderNotifyReqDTO.java | 34 ---------------------------------- yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/api/notify/dto/PayRefundNotifyReqDTO.java | 40 ---------------------------------------- yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/api/notify/dto/PayTransferNotifyReqDTO.java | 34 ---------------------------------- yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/api/notify/package-info.java | 4 ---- yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/api/order/PayOrderApi.java | 40 ---------------------------------------- yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/api/order/dto/PayOrderCreateReqDTO.java | 65 ----------------------------------------------------------------- yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/api/order/dto/PayOrderRespDTO.java | 68 -------------------------------------------------------------------- yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/api/refund/PayRefundApi.java | 31 ------------------------------- yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/api/refund/dto/PayRefundCreateReqDTO.java | 57 --------------------------------------------------------- yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/api/refund/dto/PayRefundRespDTO.java | 65 ----------------------------------------------------------------- yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/api/transfer/PayTransferApi.java | 31 ------------------------------- yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/api/transfer/dto/PayTransferCreateReqDTO.java | 120 ------------------------------------------------------------------------------------------------------------------------ yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/api/transfer/dto/PayTransferCreateRespDTO.java | 28 ---------------------------- yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/api/transfer/dto/PayTransferRespDTO.java | 81 --------------------------------------------------------------------------------- yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/api/wallet/PayWalletApi.java | 29 ----------------------------- yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/api/wallet/dto/PayWalletAddBalanceReqDTO.java | 49 ------------------------------------------------- yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/api/wallet/dto/PayWalletRespDTO.java | 52 ---------------------------------------------------- yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/enums/DictTypeConstants.java | 20 -------------------- yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/enums/ErrorCodeConstants.java | 97 ------------------------------------------------------------------------------------------------- yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/enums/MessageTemplateConstants.java | 14 -------------- yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/enums/PayChannelEnum.java | 67 ------------------------------------------------------------------- yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/enums/demo/PayDemoWithdrawStatusEnum.java | 42 ------------------------------------------ yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/enums/demo/PayDemoWithdrawTypeEnum.java | 39 --------------------------------------- yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/enums/notify/PayNotifyStatusEnum.java | 32 -------------------------------- yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/enums/notify/PayNotifyTypeEnum.java | 29 ----------------------------- yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/enums/order/PayOrderStatusEnum.java | 84 ------------------------------------------------------------------------------------ yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/enums/refund/PayRefundStatusEnum.java | 32 -------------------------------- yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/enums/transfer/PayTransferStatusEnum.java | 67 ------------------------------------------------------------------- yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/enums/wallet/PayWalletBizTypeEnum.java | 45 --------------------------------------------- yudao-module-pay/yudao-module-pay-biz/pom.xml | 93 --------------------------------------------------------------------------------------------- yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/api/order/PayOrderApiImpl.java | 39 --------------------------------------- yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/api/refund/PayRefundApiImpl.java | 35 ----------------------------------- yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/api/transfer/PayTransferApiImpl.java | 49 ------------------------------------------------- yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/api/wallet/PayWalletApiImpl.java | 41 ----------------------------------------- yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/app/PayAppController.java | 108 ------------------------------------------------------------------------------------------------------------ yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/app/vo/PayAppBaseVO.java | 47 ----------------------------------------------- yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/app/vo/PayAppCreateReqVO.java | 14 -------------- yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/app/vo/PayAppPageItemRespVO.java | 26 -------------------------- yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/app/vo/PayAppPageReqVO.java | 33 --------------------------------- yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/app/vo/PayAppRespVO.java | 25 ------------------------- yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/app/vo/PayAppUpdateReqVO.java | 17 ----------------- yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/app/vo/PayAppUpdateStatusReqVO.java | 20 -------------------- yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/channel/PayChannelController.java | 82 ---------------------------------------------------------------------------------- yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/channel/vo/PayChannelBaseVO.java | 31 ------------------------------- yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/channel/vo/PayChannelCreateReqVO.java | 25 ------------------------- yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/channel/vo/PayChannelRespVO.java | 25 ------------------------- yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/channel/vo/PayChannelUpdateReqVO.java | 20 -------------------- yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/demo/PayDemoOrderController.java | 76 ---------------------------------------------------------------------------- yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/demo/PayDemoWithdrawController.http | 50 -------------------------------------------------- yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/demo/PayDemoWithdrawController.java | 63 --------------------------------------------------------------- yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/demo/vo/order/PayDemoOrderCreateReqVO.java | 16 ---------------- yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/demo/vo/order/PayDemoOrderRespVO.java | 54 ------------------------------------------------------ yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/demo/vo/withdraw/PayDemoWithdrawCreateReqVO.java | 42 ------------------------------------------ yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/demo/vo/withdraw/PayDemoWithdrawRespVO.java | 47 ----------------------------------------------- yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/notify/PayNotifyController.java | 169 ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/notify/vo/PayNotifyTaskDetailRespVO.java | 42 ------------------------------------------ yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/notify/vo/PayNotifyTaskPageReqVO.java | 45 --------------------------------------------- yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/notify/vo/PayNotifyTaskRespVO.java | 56 -------------------------------------------------------- yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/order/PayOrderController.java | 145 ------------------------------------------------------------------------------------------------------------------------------------------------- yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/order/vo/PayOrderBaseVO.java | 89 ----------------------------------------------------------------------------------------- yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/order/vo/PayOrderDetailsRespVO.java | 45 --------------------------------------------- yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/order/vo/PayOrderExcelVO.java | 67 ------------------------------------------------------------------- yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/order/vo/PayOrderExportReqVO.java | 37 ------------------------------------- yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/order/vo/PayOrderPageItemRespVO.java | 25 ------------------------- yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/order/vo/PayOrderPageReqVO.java | 42 ------------------------------------------ yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/order/vo/PayOrderRespVO.java | 22 ---------------------- yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/order/vo/PayOrderSubmitReqVO.java | 33 --------------------------------- yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/order/vo/PayOrderSubmitRespVO.java | 18 ------------------ yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/refund/PayRefundController.java | 90 ------------------------------------------------------------------------------------------ yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/refund/vo/PayRefundBaseVO.java | 78 ------------------------------------------------------------------------------ yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/refund/vo/PayRefundDetailsRespVO.java | 40 ---------------------------------------- yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/refund/vo/PayRefundExcelVO.java | 61 ------------------------------------------------------------- yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/refund/vo/PayRefundExportReqVO.java | 40 ---------------------------------------- yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/refund/vo/PayRefundPageItemRespVO.java | 25 ------------------------- yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/refund/vo/PayRefundPageReqVO.java | 45 --------------------------------------------- yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/transfer/PayTransferController.java | 91 ------------------------------------------------------------------------------------------- yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/transfer/vo/PayTransferCreateRespVO.java | 16 ---------------- yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/transfer/vo/PayTransferPageReqVO.java | 45 --------------------------------------------- yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/transfer/vo/PayTransferRespVO.java | 100 ---------------------------------------------------------------------------------------------------- yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/wallet/PayWalletController.java | 70 ---------------------------------------------------------------------- yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/wallet/PayWalletRechargeController.java | 59 ----------------------------------------------------------- yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/wallet/PayWalletRechargePackageController.java | 75 --------------------------------------------------------------------------- yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/wallet/PayWalletTransactionController.java | 43 ------------------------------------------- yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/wallet/vo/rechargepackage/WalletRechargePackageBaseVO.java | 31 ------------------------------- yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/wallet/vo/rechargepackage/WalletRechargePackageCreateReqVO.java | 14 -------------- yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/wallet/vo/rechargepackage/WalletRechargePackagePageReqVO.java | 30 ------------------------------ yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/wallet/vo/rechargepackage/WalletRechargePackageRespVO.java | 19 ------------------- yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/wallet/vo/rechargepackage/WalletRechargePackageUpdateReqVO.java | 20 -------------------- yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/wallet/vo/transaction/PayWalletTransactionPageReqVO.java | 23 ----------------------- yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/wallet/vo/transaction/PayWalletTransactionRespVO.java | 35 ----------------------------------- yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/wallet/vo/wallet/PayWalletBaseVO.java | 39 --------------------------------------- yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/wallet/vo/wallet/PayWalletPageReqVO.java | 33 --------------------------------- yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/wallet/vo/wallet/PayWalletRespVO.java | 22 ---------------------- yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/wallet/vo/wallet/PayWalletUpdateBalanceReqVO.java | 19 ------------------- yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/wallet/vo/wallet/PayWalletUserReqVO.java | 16 ---------------- yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/app/channel/AppPayChannelController.java | 39 --------------------------------------- yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/app/order/AppPayOrderController.http | 63 --------------------------------------------------------------- yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/app/order/AppPayOrderController.java | 81 --------------------------------------------------------------------------------- yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/app/order/vo/AppPayOrderSubmitReqVO.java | 15 --------------- yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/app/order/vo/AppPayOrderSubmitRespVO.java | 15 --------------- yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/app/transfer/AppPayTransferController.java | 36 ------------------------------------ yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/app/wallet/AppPayWalletController.java | 42 ------------------------------------------ yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/app/wallet/AppPayWalletRechargeController.java | 72 ------------------------------------------------------------------------ yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/app/wallet/AppPayWalletRechargePackageController.java | 42 ------------------------------------------ yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/app/wallet/AppPayWalletTransactionController.java | 61 ------------------------------------------------------------- yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/app/wallet/vo/recharge/AppPayWalletPackageRespVO.java | 20 -------------------- yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/app/wallet/vo/recharge/AppPayWalletRechargeCreateReqVO.java | 26 -------------------------- yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/app/wallet/vo/recharge/AppPayWalletRechargeCreateRespVO.java | 16 ---------------- yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/app/wallet/vo/recharge/AppPayWalletRechargeRespVO.java | 42 ------------------------------------------ yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/app/wallet/vo/transaction/AppPayWalletTransactionPageReqVO.java | 31 ------------------------------- yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/app/wallet/vo/transaction/AppPayWalletTransactionRespVO.java | 24 ------------------------ yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/app/wallet/vo/transaction/AppPayWalletTransactionSummaryRespVO.java | 16 ---------------- yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/app/wallet/vo/wallet/AppPayWalletRespVO.java | 19 ------------------- yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/package-info.java | 6 ------ yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/convert/app/PayAppConvert.java | 49 ------------------------------------------------- yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/convert/channel/PayChannelConvert.java | 28 ---------------------------- yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/convert/order/PayOrderConvert.java | 74 -------------------------------------------------------------------------- yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/convert/package-info.java | 6 ------ yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/convert/refund/PayRefundConvert.java | 53 ----------------------------------------------------- yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/convert/wallet/PayWalletConvert.java | 21 --------------------- yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/convert/wallet/PayWalletRechargeConvert.java | 43 ------------------------------------------- yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/convert/wallet/PayWalletRechargePackageConvert.java | 29 ----------------------------- yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/convert/wallet/PayWalletTransactionConvert.java | 20 -------------------- yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/convert/《芋道 Spring Boot 对象转换 MapStruct 入门》.md | 1 - yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/dataobject/app/PayAppDO.java | 66 ------------------------------------------------------------------ yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/dataobject/channel/PayChannelDO.java | 118 ---------------------------------------------------------------------------------------------------------------------- yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/dataobject/demo/PayDemoOrderDO.java | 87 --------------------------------------------------------------------------------------- yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/dataobject/demo/PayDemoWithdrawDO.java | 84 ------------------------------------------------------------------------------------ yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/dataobject/notify/PayNotifyLogDO.java | 51 --------------------------------------------------- yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/dataobject/notify/PayNotifyTaskDO.java | 106 ---------------------------------------------------------------------------------------------------------- yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/dataobject/order/PayOrderDO.java | 138 ------------------------------------------------------------------------------------------------------------------------------------------ yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/dataobject/order/PayOrderExtensionDO.java | 96 ------------------------------------------------------------------------------------------------ yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/dataobject/refund/PayRefundDO.java | 160 ---------------------------------------------------------------------------------------------------------------------------------------------------------------- yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/dataobject/transfer/PayTransferDO.java | 146 -------------------------------------------------------------------------------------------------------------------------------------------------- yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/dataobject/wallet/PayWalletDO.java | 59 ----------------------------------------------------------- yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/dataobject/wallet/PayWalletRechargeDO.java | 116 -------------------------------------------------------------------------------------------------------------------- yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/dataobject/wallet/PayWalletRechargePackageDO.java | 47 ----------------------------------------------- yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/dataobject/wallet/PayWalletTransactionDO.java | 66 ------------------------------------------------------------------ yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/app/PayAppMapper.java | 26 -------------------------- yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/channel/PayChannelMapper.java | 31 ------------------------------- yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/demo/PayDemoOrderMapper.java | 28 ---------------------------- yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/demo/PayDemoWithdrawMapper.java | 24 ------------------------ yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/notify/PayNotifyLogMapper.java | 16 ---------------- yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/notify/PayNotifyTaskMapper.java | 46 ---------------------------------------------- yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/order/PayOrderExtensionMapper.java | 38 -------------------------------------- yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/order/PayOrderMapper.java | 62 -------------------------------------------------------------- yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/refund/PayRefundMapper.java | 78 ------------------------------------------------------------------------------ yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/transfer/PayTransferMapper.java | 65 ----------------------------------------------------------------- yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/wallet/PayWalletMapper.java | 134 -------------------------------------------------------------------------------------------------------------------------------------- yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/wallet/PayWalletRechargeMapper.java | 31 ------------------------------- yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/wallet/PayWalletRechargePackageMapper.java | 32 -------------------------------- yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/wallet/PayWalletTransactionMapper.java | 67 ------------------------------------------------------------------- yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/redis/RedisKeyConstants.java | 36 ------------------------------------ yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/redis/no/PayNoRedisDAO.java | 39 --------------------------------------- yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/redis/notify/PayNotifyLockRedisDAO.java | 39 --------------------------------------- yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/redis/wallet/PayWalletLockRedisDAO.java | 42 ------------------------------------------ yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/job/config/PayJobConfiguration.java | 28 ---------------------------- yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/job/core/package-info.java | 4 ---- yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/package-info.java | 6 ------ yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/config/PayConfiguration.java | 18 ------------------ yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/config/PayProperties.java | 69 --------------------------------------------------------------------- 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/framework/web/config/PayWebConfiguration.java | 24 ------------------------ yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/web/package-info.java | 4 ---- yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/job/notify/PayNotifyJob.java | 31 ------------------------------- yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/job/order/PayOrderExpireJob.java | 31 ------------------------------- yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/job/order/PayOrderSyncJob.java | 43 ------------------------------------------- yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/job/refund/PayRefundSyncJob.java | 31 ------------------------------- yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/job/transfer/PayTransferSyncJob.java | 30 ------------------------------ yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/package-info.java | 10 ---------- yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/app/PayAppService.java | 115 ------------------------------------------------------------------------------------------------------------------- yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/app/PayAppServiceImpl.java | 167 ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/channel/PayChannelService.java | 104 -------------------------------------------------------------------------------------------------------- yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/channel/PayChannelServiceImpl.java | 165 --------------------------------------------------------------------------------------------------------------------------------------------------------------------- yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/demo/PayDemoOrderService.java | 67 ------------------------------------------------------------------- yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/demo/PayDemoOrderServiceImpl.java | 265 ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/demo/PayDemoWithdrawService.java | 48 ------------------------------------------------ yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/demo/PayDemoWithdrawServiceImpl.java | 195 --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/notify/PayNotifyService.java | 57 --------------------------------------------------------- yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/notify/PayNotifyServiceImpl.java | 323 ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/order/PayOrderService.java | 159 --------------------------------------------------------------------------------------------------------------------------------------------------------------- yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/order/PayOrderServiceImpl.java | 605 ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/refund/PayRefundService.java | 82 ---------------------------------------------------------------------------------- yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/refund/PayRefundServiceImpl.java | 331 ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/transfer/PayTransferService.java | 70 ---------------------------------------------------------------------- yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/transfer/PayTransferServiceImpl.java | 319 ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/wallet/PayWalletRechargePackageService.java | 71 ----------------------------------------------------------------------- yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/wallet/PayWalletRechargePackageServiceImpl.java | 113 ----------------------------------------------------------------------------------------------------------------- yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/wallet/PayWalletRechargeService.java | 64 ---------------------------------------------------------------- yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/wallet/PayWalletRechargeServiceImpl.java | 331 ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/wallet/PayWalletService.java | 99 --------------------------------------------------------------------------------------------------- yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/wallet/PayWalletServiceImpl.java | 233 ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/wallet/PayWalletTransactionService.java | 74 -------------------------------------------------------------------------- yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/wallet/PayWalletTransactionServiceImpl.java | 95 ----------------------------------------------------------------------------------------------- yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/wallet/bo/WalletTransactionCreateReqBO.java | 59 ----------------------------------------------------------- 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/app/PayAppServiceTest.java | 260 -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- yudao-module-pay/yudao-module-pay-biz/src/test/java/cn/iocoder/yudao/module/pay/service/channel/PayChannelServiceTest.java | 337 ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- yudao-module-pay/yudao-module-pay-biz/src/test/java/cn/iocoder/yudao/module/pay/service/notify/PayNotifyServiceTest.java | 353 ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- yudao-module-pay/yudao-module-pay-biz/src/test/java/cn/iocoder/yudao/module/pay/service/order/PayOrderServiceTest.java | 1104 ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ yudao-module-pay/yudao-module-pay-biz/src/test/java/cn/iocoder/yudao/module/pay/service/refund/PayRefundServiceTest.java | 699 --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- yudao-module-pay/yudao-module-pay-biz/src/test/resources/application-unit-test.yaml | 48 ------------------------------------------------ yudao-module-pay/yudao-module-pay-biz/src/test/resources/logback.xml | 4 ---- yudao-module-pay/yudao-module-pay-biz/src/test/resources/sql/clean.sql | 7 ------- yudao-module-pay/yudao-module-pay-biz/src/test/resources/sql/create_tables.sql | 147 --------------------------------------------------------------------------------------------------------------------------------------------------- yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/controller/admin/socail/SocialUserController.java | 6 +++--- yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/convert/social/SocialUserConvert.java | 17 ----------------- yudao-server/pom.xml | 2 +- 492 files changed, 19750 insertions(+), 19824 deletions(-) create mode 100644 yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/api/notify/dto/PayOrderNotifyReqDTO.java create mode 100644 yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/api/notify/dto/PayRefundNotifyReqDTO.java create mode 100644 yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/api/notify/dto/PayTransferNotifyReqDTO.java create mode 100644 yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/api/notify/package-info.java create mode 100644 yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/api/order/PayOrderApi.java create mode 100644 yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/api/order/PayOrderApiImpl.java create mode 100644 yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/api/order/dto/PayOrderCreateReqDTO.java create mode 100644 yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/api/order/dto/PayOrderRespDTO.java create mode 100644 yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/api/package-info.java create mode 100644 yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/api/refund/PayRefundApi.java create mode 100644 yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/api/refund/PayRefundApiImpl.java create mode 100644 yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/api/refund/dto/PayRefundCreateReqDTO.java create mode 100644 yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/api/refund/dto/PayRefundRespDTO.java create mode 100644 yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/api/transfer/PayTransferApi.java create mode 100644 yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/api/transfer/PayTransferApiImpl.java create mode 100644 yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/api/transfer/dto/PayTransferCreateReqDTO.java create mode 100644 yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/api/transfer/dto/PayTransferCreateRespDTO.java create mode 100644 yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/api/transfer/dto/PayTransferRespDTO.java create mode 100644 yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/api/wallet/PayWalletApi.java create mode 100644 yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/api/wallet/PayWalletApiImpl.java create mode 100644 yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/api/wallet/dto/PayWalletAddBalanceReqDTO.java create mode 100644 yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/api/wallet/dto/PayWalletRespDTO.java create mode 100644 yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/app/PayAppController.java create mode 100644 yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/app/vo/PayAppBaseVO.java create mode 100644 yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/app/vo/PayAppCreateReqVO.java create mode 100644 yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/app/vo/PayAppPageItemRespVO.java create mode 100644 yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/app/vo/PayAppPageReqVO.java create mode 100644 yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/app/vo/PayAppRespVO.java create mode 100644 yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/app/vo/PayAppUpdateReqVO.java create mode 100644 yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/app/vo/PayAppUpdateStatusReqVO.java create mode 100644 yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/channel/PayChannelController.java create mode 100644 yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/channel/vo/PayChannelBaseVO.java create mode 100644 yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/channel/vo/PayChannelCreateReqVO.java create mode 100644 yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/channel/vo/PayChannelRespVO.java create mode 100644 yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/channel/vo/PayChannelUpdateReqVO.java create mode 100644 yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/demo/PayDemoOrderController.java create mode 100644 yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/demo/PayDemoWithdrawController.http create mode 100644 yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/demo/PayDemoWithdrawController.java create mode 100644 yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/demo/vo/order/PayDemoOrderCreateReqVO.java create mode 100644 yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/demo/vo/order/PayDemoOrderRespVO.java create mode 100644 yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/demo/vo/withdraw/PayDemoWithdrawCreateReqVO.java create mode 100644 yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/demo/vo/withdraw/PayDemoWithdrawRespVO.java create mode 100644 yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/notify/PayNotifyController.java create mode 100644 yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/notify/vo/PayNotifyTaskDetailRespVO.java create mode 100644 yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/notify/vo/PayNotifyTaskPageReqVO.java create mode 100644 yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/notify/vo/PayNotifyTaskRespVO.java create mode 100755 yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/order/PayOrderController.java create mode 100755 yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/order/vo/PayOrderBaseVO.java create mode 100644 yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/order/vo/PayOrderDetailsRespVO.java create mode 100755 yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/order/vo/PayOrderExcelVO.java create mode 100755 yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/order/vo/PayOrderExportReqVO.java create mode 100755 yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/order/vo/PayOrderPageItemRespVO.java create mode 100755 yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/order/vo/PayOrderPageReqVO.java create mode 100644 yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/order/vo/PayOrderRespVO.java create mode 100644 yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/order/vo/PayOrderSubmitReqVO.java create mode 100644 yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/order/vo/PayOrderSubmitRespVO.java create mode 100755 yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/refund/PayRefundController.java create mode 100755 yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/refund/vo/PayRefundBaseVO.java create mode 100755 yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/refund/vo/PayRefundDetailsRespVO.java create mode 100755 yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/refund/vo/PayRefundExcelVO.java create mode 100755 yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/refund/vo/PayRefundExportReqVO.java create mode 100755 yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/refund/vo/PayRefundPageItemRespVO.java create mode 100755 yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/refund/vo/PayRefundPageReqVO.java create mode 100644 yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/transfer/PayTransferController.java create mode 100644 yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/transfer/vo/PayTransferCreateRespVO.java create mode 100644 yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/transfer/vo/PayTransferPageReqVO.java create mode 100644 yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/transfer/vo/PayTransferRespVO.java create mode 100644 yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/wallet/PayWalletController.java create mode 100644 yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/wallet/PayWalletRechargeController.java create mode 100644 yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/wallet/PayWalletRechargePackageController.java create mode 100644 yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/wallet/PayWalletTransactionController.java create mode 100644 yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/wallet/vo/rechargepackage/WalletRechargePackageBaseVO.java create mode 100644 yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/wallet/vo/rechargepackage/WalletRechargePackageCreateReqVO.java create mode 100644 yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/wallet/vo/rechargepackage/WalletRechargePackagePageReqVO.java create mode 100644 yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/wallet/vo/rechargepackage/WalletRechargePackageRespVO.java create mode 100644 yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/wallet/vo/rechargepackage/WalletRechargePackageUpdateReqVO.java create mode 100644 yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/wallet/vo/transaction/PayWalletTransactionPageReqVO.java create mode 100644 yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/wallet/vo/transaction/PayWalletTransactionRespVO.java create mode 100644 yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/wallet/vo/wallet/PayWalletBaseVO.java create mode 100644 yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/wallet/vo/wallet/PayWalletPageReqVO.java create mode 100644 yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/wallet/vo/wallet/PayWalletRespVO.java create mode 100644 yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/wallet/vo/wallet/PayWalletUpdateBalanceReqVO.java create mode 100644 yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/wallet/vo/wallet/PayWalletUserReqVO.java create mode 100644 yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/app/channel/AppPayChannelController.java create mode 100644 yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/app/order/AppPayOrderController.http create mode 100644 yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/app/order/AppPayOrderController.java create mode 100644 yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/app/order/vo/AppPayOrderSubmitReqVO.java create mode 100644 yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/app/order/vo/AppPayOrderSubmitRespVO.java create mode 100644 yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/app/transfer/AppPayTransferController.java create mode 100644 yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/app/wallet/AppPayWalletController.java create mode 100644 yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/app/wallet/AppPayWalletRechargeController.java create mode 100644 yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/app/wallet/AppPayWalletRechargePackageController.java create mode 100644 yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/app/wallet/AppPayWalletTransactionController.java create mode 100644 yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/app/wallet/vo/recharge/AppPayWalletPackageRespVO.java create mode 100644 yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/app/wallet/vo/recharge/AppPayWalletRechargeCreateReqVO.java create mode 100644 yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/app/wallet/vo/recharge/AppPayWalletRechargeCreateRespVO.java create mode 100644 yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/app/wallet/vo/recharge/AppPayWalletRechargeRespVO.java create mode 100644 yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/app/wallet/vo/transaction/AppPayWalletTransactionPageReqVO.java create mode 100644 yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/app/wallet/vo/transaction/AppPayWalletTransactionRespVO.java create mode 100644 yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/app/wallet/vo/transaction/AppPayWalletTransactionSummaryRespVO.java create mode 100644 yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/app/wallet/vo/wallet/AppPayWalletRespVO.java create mode 100644 yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/package-info.java create mode 100644 yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/convert/app/PayAppConvert.java create mode 100644 yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/convert/channel/PayChannelConvert.java create mode 100755 yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/convert/order/PayOrderConvert.java create mode 100644 yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/convert/package-info.java create mode 100755 yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/convert/refund/PayRefundConvert.java create mode 100644 yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/convert/wallet/PayWalletConvert.java create mode 100644 yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/convert/wallet/PayWalletRechargeConvert.java create mode 100644 yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/convert/wallet/PayWalletRechargePackageConvert.java create mode 100644 yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/convert/wallet/PayWalletTransactionConvert.java create mode 100644 yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/convert/《芋道 Spring Boot 对象转换 MapStruct 入门》.md create mode 100644 yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/dal/dataobject/app/PayAppDO.java create mode 100644 yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/dal/dataobject/channel/PayChannelDO.java create mode 100644 yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/dal/dataobject/demo/PayDemoOrderDO.java create mode 100644 yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/dal/dataobject/demo/PayDemoWithdrawDO.java create mode 100644 yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/dal/dataobject/notify/PayNotifyLogDO.java create mode 100644 yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/dal/dataobject/notify/PayNotifyTaskDO.java create mode 100644 yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/dal/dataobject/order/PayOrderDO.java create mode 100644 yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/dal/dataobject/order/PayOrderExtensionDO.java create mode 100644 yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/dal/dataobject/refund/PayRefundDO.java create mode 100644 yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/dal/dataobject/transfer/PayTransferDO.java create mode 100644 yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/dal/dataobject/wallet/PayWalletDO.java create mode 100644 yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/dal/dataobject/wallet/PayWalletRechargeDO.java create mode 100644 yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/dal/dataobject/wallet/PayWalletRechargePackageDO.java create mode 100644 yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/dal/dataobject/wallet/PayWalletTransactionDO.java create mode 100644 yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/app/PayAppMapper.java create mode 100644 yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/channel/PayChannelMapper.java create mode 100644 yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/demo/PayDemoOrderMapper.java create mode 100644 yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/demo/PayDemoWithdrawMapper.java create mode 100644 yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/notify/PayNotifyLogMapper.java create mode 100644 yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/notify/PayNotifyTaskMapper.java create mode 100755 yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/order/PayOrderExtensionMapper.java create mode 100755 yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/order/PayOrderMapper.java create mode 100755 yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/refund/PayRefundMapper.java create mode 100644 yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/transfer/PayTransferMapper.java create mode 100644 yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/wallet/PayWalletMapper.java create mode 100644 yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/wallet/PayWalletRechargeMapper.java create mode 100644 yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/wallet/PayWalletRechargePackageMapper.java create mode 100644 yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/wallet/PayWalletTransactionMapper.java create mode 100644 yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/dal/redis/RedisKeyConstants.java create mode 100644 yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/dal/redis/no/PayNoRedisDAO.java create mode 100644 yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/dal/redis/notify/PayNotifyLockRedisDAO.java create mode 100644 yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/dal/redis/wallet/PayWalletLockRedisDAO.java create mode 100644 yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/enums/DictTypeConstants.java create mode 100644 yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/enums/ErrorCodeConstants.java create mode 100644 yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/enums/MessageTemplateConstants.java create mode 100644 yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/enums/PayChannelEnum.java create mode 100644 yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/enums/demo/PayDemoWithdrawStatusEnum.java create mode 100644 yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/enums/demo/PayDemoWithdrawTypeEnum.java create mode 100644 yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/enums/notify/PayNotifyStatusEnum.java create mode 100644 yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/enums/notify/PayNotifyTypeEnum.java create mode 100644 yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/enums/order/PayOrderStatusEnum.java create mode 100644 yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/enums/refund/PayRefundStatusEnum.java create mode 100644 yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/enums/transfer/PayTransferStatusEnum.java create mode 100644 yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/enums/wallet/PayWalletBizTypeEnum.java create mode 100644 yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/framework/job/config/PayJobConfiguration.java create mode 100644 yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/framework/job/core/package-info.java create mode 100644 yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/framework/package-info.java create mode 100644 yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/config/PayConfiguration.java create mode 100644 yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/config/PayProperties.java create mode 100644 yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/PayClient.java create mode 100644 yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/PayClientConfig.java create mode 100644 yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/PayClientFactory.java create mode 100644 yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/dto/order/PayOrderRespDTO.java create mode 100644 yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/dto/order/PayOrderUnifiedReqDTO.java create mode 100644 yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/dto/refund/PayRefundRespDTO.java create mode 100644 yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/dto/refund/PayRefundUnifiedReqDTO.java create mode 100644 yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/dto/transfer/PayTransferRespDTO.java create mode 100644 yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/dto/transfer/PayTransferUnifiedReqDTO.java create mode 100644 yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/exception/PayClientException.java create mode 100644 yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/impl/AbstractPayClient.java create mode 100644 yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/impl/NonePayClientConfig.java create mode 100644 yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/impl/PayClientFactoryImpl.java create mode 100644 yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/impl/alipay/AbstractAlipayPayClient.java create mode 100644 yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/impl/alipay/AlipayAppPayClient.java create mode 100644 yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/impl/alipay/AlipayBarPayClient.java create mode 100644 yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/impl/alipay/AlipayPayClientConfig.java create mode 100644 yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/impl/alipay/AlipayPcPayClient.java create mode 100644 yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/impl/alipay/AlipayQrPayClient.java create mode 100644 yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/impl/alipay/AlipayWapPayClient.java create mode 100644 yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/impl/mock/MockPayClient.java create mode 100644 yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/impl/wallet/WalletPayClient.java create mode 100644 yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/impl/weixin/AbstractWxPayClient.java create mode 100644 yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/impl/weixin/WxAppPayClient.java create mode 100644 yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/impl/weixin/WxBarPayClient.java create mode 100644 yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/impl/weixin/WxLitePayClient.java create mode 100644 yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/impl/weixin/WxNativePayClient.java create mode 100644 yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/impl/weixin/WxPayClientConfig.java create mode 100644 yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/impl/weixin/WxPubPayClient.java create mode 100644 yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/impl/weixin/WxWapPayClient.java create mode 100644 yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/enums/PayOrderDisplayModeEnum.java create mode 100644 yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/package-info.java create mode 100644 yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/framework/web/config/PayWebConfiguration.java create mode 100644 yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/framework/web/package-info.java create mode 100644 yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/job/notify/PayNotifyJob.java create mode 100644 yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/job/order/PayOrderExpireJob.java create mode 100644 yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/job/order/PayOrderSyncJob.java create mode 100644 yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/job/refund/PayRefundSyncJob.java create mode 100644 yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/job/transfer/PayTransferSyncJob.java create mode 100644 yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/package-info.java create mode 100644 yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/service/app/PayAppService.java create mode 100644 yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/service/app/PayAppServiceImpl.java create mode 100644 yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/service/channel/PayChannelService.java create mode 100644 yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/service/channel/PayChannelServiceImpl.java create mode 100644 yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/service/demo/PayDemoOrderService.java create mode 100644 yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/service/demo/PayDemoOrderServiceImpl.java create mode 100644 yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/service/demo/PayDemoWithdrawService.java create mode 100644 yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/service/demo/PayDemoWithdrawServiceImpl.java create mode 100644 yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/service/notify/PayNotifyService.java create mode 100644 yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/service/notify/PayNotifyServiceImpl.java create mode 100755 yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/service/order/PayOrderService.java create mode 100755 yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/service/order/PayOrderServiceImpl.java create mode 100755 yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/service/refund/PayRefundService.java create mode 100755 yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/service/refund/PayRefundServiceImpl.java create mode 100644 yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/service/transfer/PayTransferService.java create mode 100644 yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/service/transfer/PayTransferServiceImpl.java create mode 100644 yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/service/wallet/PayWalletRechargePackageService.java create mode 100644 yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/service/wallet/PayWalletRechargePackageServiceImpl.java create mode 100644 yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/service/wallet/PayWalletRechargeService.java create mode 100644 yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/service/wallet/PayWalletRechargeServiceImpl.java create mode 100644 yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/service/wallet/PayWalletService.java create mode 100644 yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/service/wallet/PayWalletServiceImpl.java create mode 100644 yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/service/wallet/PayWalletTransactionService.java create mode 100644 yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/service/wallet/PayWalletTransactionServiceImpl.java create mode 100644 yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/service/wallet/bo/WalletTransactionCreateReqBO.java create mode 100644 yudao-module-pay/src/test/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/impl/PayClientFactoryImplIntegrationTest.java create mode 100644 yudao-module-pay/src/test/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/impl/alipay/AbstractAlipayClientTest.java create mode 100644 yudao-module-pay/src/test/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/impl/alipay/AlipayBarPayClientTest.java create mode 100644 yudao-module-pay/src/test/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/impl/alipay/AlipayPcPayClientTest.java create mode 100644 yudao-module-pay/src/test/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/impl/alipay/AlipayQrPayClientTest.java create mode 100644 yudao-module-pay/src/test/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/impl/alipay/AlipayWapPayClientTest.java create mode 100644 yudao-module-pay/src/test/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/impl/weixin/WxBarPayClientIntegrationTest.java create mode 100644 yudao-module-pay/src/test/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/impl/weixin/WxNativePayClientIntegrationTest.java create mode 100644 yudao-module-pay/src/test/java/cn/iocoder/yudao/module/pay/service/app/PayAppServiceTest.java create mode 100644 yudao-module-pay/src/test/java/cn/iocoder/yudao/module/pay/service/channel/PayChannelServiceTest.java create mode 100644 yudao-module-pay/src/test/java/cn/iocoder/yudao/module/pay/service/notify/PayNotifyServiceTest.java create mode 100755 yudao-module-pay/src/test/java/cn/iocoder/yudao/module/pay/service/order/PayOrderServiceTest.java create mode 100755 yudao-module-pay/src/test/java/cn/iocoder/yudao/module/pay/service/refund/PayRefundServiceTest.java create mode 100644 yudao-module-pay/src/test/resources/application-unit-test.yaml create mode 100644 yudao-module-pay/src/test/resources/logback.xml create mode 100644 yudao-module-pay/src/test/resources/sql/clean.sql create mode 100644 yudao-module-pay/src/test/resources/sql/create_tables.sql delete mode 100644 yudao-module-pay/yudao-module-pay-api/pom.xml delete mode 100644 yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/api/notify/dto/PayOrderNotifyReqDTO.java delete mode 100644 yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/api/notify/dto/PayRefundNotifyReqDTO.java delete mode 100644 yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/api/notify/dto/PayTransferNotifyReqDTO.java delete mode 100644 yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/api/notify/package-info.java delete mode 100644 yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/api/order/PayOrderApi.java delete mode 100644 yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/api/order/dto/PayOrderCreateReqDTO.java delete mode 100644 yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/api/order/dto/PayOrderRespDTO.java delete mode 100644 yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/api/refund/PayRefundApi.java delete mode 100644 yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/api/refund/dto/PayRefundCreateReqDTO.java delete mode 100644 yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/api/refund/dto/PayRefundRespDTO.java delete mode 100644 yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/api/transfer/PayTransferApi.java delete mode 100644 yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/api/transfer/dto/PayTransferCreateReqDTO.java delete mode 100644 yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/api/transfer/dto/PayTransferCreateRespDTO.java delete mode 100644 yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/api/transfer/dto/PayTransferRespDTO.java delete mode 100644 yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/api/wallet/PayWalletApi.java delete mode 100644 yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/api/wallet/dto/PayWalletAddBalanceReqDTO.java delete mode 100644 yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/api/wallet/dto/PayWalletRespDTO.java delete mode 100644 yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/enums/DictTypeConstants.java delete mode 100644 yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/enums/ErrorCodeConstants.java delete mode 100644 yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/enums/MessageTemplateConstants.java delete mode 100644 yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/enums/PayChannelEnum.java delete mode 100644 yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/enums/demo/PayDemoWithdrawStatusEnum.java delete mode 100644 yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/enums/demo/PayDemoWithdrawTypeEnum.java delete mode 100644 yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/enums/notify/PayNotifyStatusEnum.java delete mode 100644 yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/enums/notify/PayNotifyTypeEnum.java delete mode 100644 yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/enums/order/PayOrderStatusEnum.java delete mode 100644 yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/enums/refund/PayRefundStatusEnum.java delete mode 100644 yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/enums/transfer/PayTransferStatusEnum.java delete mode 100644 yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/enums/wallet/PayWalletBizTypeEnum.java delete mode 100644 yudao-module-pay/yudao-module-pay-biz/pom.xml delete mode 100644 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/api/order/PayOrderApiImpl.java delete mode 100644 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/api/refund/PayRefundApiImpl.java delete mode 100644 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/api/transfer/PayTransferApiImpl.java delete mode 100644 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/api/wallet/PayWalletApiImpl.java delete mode 100644 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/app/PayAppController.java delete mode 100644 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/app/vo/PayAppBaseVO.java delete mode 100644 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/app/vo/PayAppCreateReqVO.java delete mode 100644 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/app/vo/PayAppPageItemRespVO.java delete mode 100644 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/app/vo/PayAppPageReqVO.java delete mode 100644 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/app/vo/PayAppRespVO.java delete mode 100644 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/app/vo/PayAppUpdateReqVO.java delete mode 100644 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/app/vo/PayAppUpdateStatusReqVO.java delete mode 100644 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/channel/PayChannelController.java delete mode 100644 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/channel/vo/PayChannelBaseVO.java delete mode 100644 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/channel/vo/PayChannelCreateReqVO.java delete mode 100644 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/channel/vo/PayChannelRespVO.java delete mode 100644 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/channel/vo/PayChannelUpdateReqVO.java delete mode 100644 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/demo/PayDemoOrderController.java delete mode 100644 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/demo/PayDemoWithdrawController.http delete mode 100644 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/demo/PayDemoWithdrawController.java delete mode 100644 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/demo/vo/order/PayDemoOrderCreateReqVO.java delete mode 100644 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/demo/vo/order/PayDemoOrderRespVO.java delete mode 100644 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/demo/vo/withdraw/PayDemoWithdrawCreateReqVO.java delete mode 100644 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/demo/vo/withdraw/PayDemoWithdrawRespVO.java delete mode 100644 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/notify/PayNotifyController.java delete mode 100644 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/notify/vo/PayNotifyTaskDetailRespVO.java delete mode 100644 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/notify/vo/PayNotifyTaskPageReqVO.java delete mode 100644 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/notify/vo/PayNotifyTaskRespVO.java delete mode 100755 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/order/PayOrderController.java delete mode 100755 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/order/vo/PayOrderBaseVO.java delete mode 100644 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/order/vo/PayOrderDetailsRespVO.java delete mode 100755 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/order/vo/PayOrderExcelVO.java delete mode 100755 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/order/vo/PayOrderExportReqVO.java delete mode 100755 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/order/vo/PayOrderPageItemRespVO.java delete mode 100755 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/order/vo/PayOrderPageReqVO.java delete mode 100644 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/order/vo/PayOrderRespVO.java delete mode 100644 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/order/vo/PayOrderSubmitReqVO.java delete mode 100644 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/order/vo/PayOrderSubmitRespVO.java delete mode 100755 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/refund/PayRefundController.java delete mode 100755 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/refund/vo/PayRefundBaseVO.java delete mode 100755 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/refund/vo/PayRefundDetailsRespVO.java delete mode 100755 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/refund/vo/PayRefundExcelVO.java delete mode 100755 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/refund/vo/PayRefundExportReqVO.java delete mode 100755 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/refund/vo/PayRefundPageItemRespVO.java delete mode 100755 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/refund/vo/PayRefundPageReqVO.java delete mode 100644 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/transfer/PayTransferController.java delete mode 100644 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/transfer/vo/PayTransferCreateRespVO.java delete mode 100644 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/transfer/vo/PayTransferPageReqVO.java delete mode 100644 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/transfer/vo/PayTransferRespVO.java delete mode 100644 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/wallet/PayWalletController.java delete mode 100644 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/wallet/PayWalletRechargeController.java delete mode 100644 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/wallet/PayWalletRechargePackageController.java delete mode 100644 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/wallet/PayWalletTransactionController.java delete mode 100644 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/wallet/vo/rechargepackage/WalletRechargePackageBaseVO.java delete mode 100644 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/wallet/vo/rechargepackage/WalletRechargePackageCreateReqVO.java delete mode 100644 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/wallet/vo/rechargepackage/WalletRechargePackagePageReqVO.java delete mode 100644 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/wallet/vo/rechargepackage/WalletRechargePackageRespVO.java delete mode 100644 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/wallet/vo/rechargepackage/WalletRechargePackageUpdateReqVO.java delete mode 100644 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/wallet/vo/transaction/PayWalletTransactionPageReqVO.java delete mode 100644 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/wallet/vo/transaction/PayWalletTransactionRespVO.java delete mode 100644 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/wallet/vo/wallet/PayWalletBaseVO.java delete mode 100644 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/wallet/vo/wallet/PayWalletPageReqVO.java delete mode 100644 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/wallet/vo/wallet/PayWalletRespVO.java delete mode 100644 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/wallet/vo/wallet/PayWalletUpdateBalanceReqVO.java delete mode 100644 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/wallet/vo/wallet/PayWalletUserReqVO.java delete mode 100644 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/app/channel/AppPayChannelController.java delete mode 100644 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/app/order/AppPayOrderController.http delete mode 100644 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/app/order/AppPayOrderController.java delete mode 100644 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/app/order/vo/AppPayOrderSubmitReqVO.java delete mode 100644 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/app/order/vo/AppPayOrderSubmitRespVO.java delete mode 100644 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/app/transfer/AppPayTransferController.java delete mode 100644 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/app/wallet/AppPayWalletController.java delete mode 100644 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/app/wallet/AppPayWalletRechargeController.java delete mode 100644 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/app/wallet/AppPayWalletRechargePackageController.java delete mode 100644 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/app/wallet/AppPayWalletTransactionController.java delete mode 100644 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/app/wallet/vo/recharge/AppPayWalletPackageRespVO.java delete mode 100644 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/app/wallet/vo/recharge/AppPayWalletRechargeCreateReqVO.java delete mode 100644 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/app/wallet/vo/recharge/AppPayWalletRechargeCreateRespVO.java delete mode 100644 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/app/wallet/vo/recharge/AppPayWalletRechargeRespVO.java delete mode 100644 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/app/wallet/vo/transaction/AppPayWalletTransactionPageReqVO.java delete mode 100644 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/app/wallet/vo/transaction/AppPayWalletTransactionRespVO.java delete mode 100644 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/app/wallet/vo/transaction/AppPayWalletTransactionSummaryRespVO.java delete mode 100644 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/app/wallet/vo/wallet/AppPayWalletRespVO.java delete mode 100644 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/package-info.java delete mode 100644 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/convert/app/PayAppConvert.java delete mode 100644 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/convert/channel/PayChannelConvert.java delete mode 100755 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/convert/order/PayOrderConvert.java delete mode 100644 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/convert/package-info.java delete mode 100755 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/convert/refund/PayRefundConvert.java delete mode 100644 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/convert/wallet/PayWalletConvert.java delete mode 100644 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/convert/wallet/PayWalletRechargeConvert.java delete mode 100644 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/convert/wallet/PayWalletRechargePackageConvert.java delete mode 100644 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/convert/wallet/PayWalletTransactionConvert.java delete mode 100644 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/convert/《芋道 Spring Boot 对象转换 MapStruct 入门》.md delete mode 100644 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/dataobject/app/PayAppDO.java delete mode 100644 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/dataobject/channel/PayChannelDO.java delete mode 100644 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/dataobject/demo/PayDemoOrderDO.java delete mode 100644 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/dataobject/demo/PayDemoWithdrawDO.java delete mode 100644 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/dataobject/notify/PayNotifyLogDO.java delete mode 100644 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/dataobject/notify/PayNotifyTaskDO.java delete mode 100644 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/dataobject/order/PayOrderDO.java delete mode 100644 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/dataobject/order/PayOrderExtensionDO.java delete mode 100644 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/dataobject/refund/PayRefundDO.java delete mode 100644 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/dataobject/transfer/PayTransferDO.java delete mode 100644 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/dataobject/wallet/PayWalletDO.java delete mode 100644 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/dataobject/wallet/PayWalletRechargeDO.java delete mode 100644 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/dataobject/wallet/PayWalletRechargePackageDO.java delete mode 100644 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/dataobject/wallet/PayWalletTransactionDO.java delete mode 100644 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/app/PayAppMapper.java delete mode 100644 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/channel/PayChannelMapper.java delete mode 100644 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/demo/PayDemoOrderMapper.java delete mode 100644 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/demo/PayDemoWithdrawMapper.java delete mode 100644 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/notify/PayNotifyLogMapper.java delete mode 100644 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/notify/PayNotifyTaskMapper.java delete mode 100755 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/order/PayOrderExtensionMapper.java delete mode 100755 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/order/PayOrderMapper.java delete mode 100755 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/refund/PayRefundMapper.java delete mode 100644 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/transfer/PayTransferMapper.java delete mode 100644 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/wallet/PayWalletMapper.java delete mode 100644 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/wallet/PayWalletRechargeMapper.java delete mode 100644 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/wallet/PayWalletRechargePackageMapper.java delete mode 100644 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/wallet/PayWalletTransactionMapper.java delete mode 100644 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/redis/RedisKeyConstants.java delete mode 100644 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/redis/no/PayNoRedisDAO.java delete mode 100644 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/redis/notify/PayNotifyLockRedisDAO.java delete mode 100644 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/redis/wallet/PayWalletLockRedisDAO.java delete mode 100644 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/job/config/PayJobConfiguration.java delete mode 100644 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/job/core/package-info.java delete mode 100644 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/package-info.java delete mode 100644 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/config/PayConfiguration.java delete mode 100644 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/config/PayProperties.java delete mode 100644 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/PayClient.java delete mode 100644 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/PayClientConfig.java delete mode 100644 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/PayClientFactory.java delete 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 delete 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 delete 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 delete 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 delete 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 delete 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 delete mode 100644 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/exception/PayClientException.java delete mode 100644 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/impl/AbstractPayClient.java delete mode 100644 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/impl/NonePayClientConfig.java delete mode 100644 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/impl/PayClientFactoryImpl.java delete 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 delete 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 delete 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 delete 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 delete 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 delete 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 delete 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 delete 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 delete 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 delete 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 delete 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 delete 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 delete 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 delete 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 delete 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 delete 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 delete 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 delete mode 100644 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/enums/PayOrderDisplayModeEnum.java delete mode 100644 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/package-info.java delete mode 100644 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/web/config/PayWebConfiguration.java delete mode 100644 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/web/package-info.java delete mode 100644 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/job/notify/PayNotifyJob.java delete mode 100644 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/job/order/PayOrderExpireJob.java delete mode 100644 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/job/order/PayOrderSyncJob.java delete mode 100644 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/job/refund/PayRefundSyncJob.java delete mode 100644 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/job/transfer/PayTransferSyncJob.java delete mode 100644 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/package-info.java delete mode 100644 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/app/PayAppService.java delete mode 100644 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/app/PayAppServiceImpl.java delete mode 100644 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/channel/PayChannelService.java delete mode 100644 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/channel/PayChannelServiceImpl.java delete mode 100644 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/demo/PayDemoOrderService.java delete mode 100644 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/demo/PayDemoOrderServiceImpl.java delete mode 100644 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/demo/PayDemoWithdrawService.java delete mode 100644 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/demo/PayDemoWithdrawServiceImpl.java delete mode 100644 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/notify/PayNotifyService.java delete mode 100644 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/notify/PayNotifyServiceImpl.java delete mode 100755 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/order/PayOrderService.java delete mode 100755 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/order/PayOrderServiceImpl.java delete mode 100755 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/refund/PayRefundService.java delete mode 100755 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/refund/PayRefundServiceImpl.java delete mode 100644 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/transfer/PayTransferService.java delete mode 100644 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/transfer/PayTransferServiceImpl.java delete mode 100644 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/wallet/PayWalletRechargePackageService.java delete mode 100644 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/wallet/PayWalletRechargePackageServiceImpl.java delete mode 100644 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/wallet/PayWalletRechargeService.java delete mode 100644 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/wallet/PayWalletRechargeServiceImpl.java delete mode 100644 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/wallet/PayWalletService.java delete mode 100644 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/wallet/PayWalletServiceImpl.java delete mode 100644 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/wallet/PayWalletTransactionService.java delete mode 100644 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/wallet/PayWalletTransactionServiceImpl.java delete mode 100644 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/wallet/bo/WalletTransactionCreateReqBO.java delete mode 100644 yudao-module-pay/yudao-module-pay-biz/src/test/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/impl/PayClientFactoryImplIntegrationTest.java delete 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 delete 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 delete 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 delete 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 delete 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 delete 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 delete 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-module-pay-biz/src/test/java/cn/iocoder/yudao/module/pay/service/app/PayAppServiceTest.java delete mode 100644 yudao-module-pay/yudao-module-pay-biz/src/test/java/cn/iocoder/yudao/module/pay/service/channel/PayChannelServiceTest.java delete mode 100644 yudao-module-pay/yudao-module-pay-biz/src/test/java/cn/iocoder/yudao/module/pay/service/notify/PayNotifyServiceTest.java delete mode 100755 yudao-module-pay/yudao-module-pay-biz/src/test/java/cn/iocoder/yudao/module/pay/service/order/PayOrderServiceTest.java delete mode 100755 yudao-module-pay/yudao-module-pay-biz/src/test/java/cn/iocoder/yudao/module/pay/service/refund/PayRefundServiceTest.java delete mode 100644 yudao-module-pay/yudao-module-pay-biz/src/test/resources/application-unit-test.yaml delete mode 100644 yudao-module-pay/yudao-module-pay-biz/src/test/resources/logback.xml delete mode 100644 yudao-module-pay/yudao-module-pay-biz/src/test/resources/sql/clean.sql delete mode 100644 yudao-module-pay/yudao-module-pay-biz/src/test/resources/sql/create_tables.sql delete mode 100644 yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/convert/social/SocialUserConvert.java diff --git a/pom.xml b/pom.xml index d8814a0..e656a28 100644 --- a/pom.xml +++ b/pom.xml @@ -38,7 +38,7 @@ ${java.version} ${java.version} 3.2.2 - 3.13.0 + 3.14.0 1.6.0 1.18.36 diff --git a/yudao-module-pay/pom.xml b/yudao-module-pay/pom.xml index c1581b6..9839308 100644 --- a/yudao-module-pay/pom.xml +++ b/yudao-module-pay/pom.xml @@ -9,11 +9,7 @@ 4.0.0 yudao-module-pay - pom - - yudao-module-pay-api - yudao-module-pay-biz - + jar ${project.artifactId} @@ -21,4 +17,73 @@ 例如说:商户、应用、支付、退款等等 + + + + cn.iocoder.boot + yudao-module-system + ${revision} + + + + + cn.iocoder.boot + yudao-spring-boot-starter-biz-tenant + + + + + cn.iocoder.boot + yudao-spring-boot-starter-security + + + + + cn.iocoder.boot + yudao-spring-boot-starter-mybatis + + + + cn.iocoder.boot + yudao-spring-boot-starter-redis + + + + + cn.iocoder.boot + yudao-spring-boot-starter-job + + + + + cn.iocoder.boot + yudao-spring-boot-starter-test + test + + + + + 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/src/main/java/cn/iocoder/yudao/module/pay/api/notify/dto/PayOrderNotifyReqDTO.java b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/api/notify/dto/PayOrderNotifyReqDTO.java new file mode 100644 index 0000000..8c01e8b --- /dev/null +++ b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/api/notify/dto/PayOrderNotifyReqDTO.java @@ -0,0 +1,34 @@ +package cn.iocoder.yudao.module.pay.api.notify.dto; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; + +/** + * 支付单的通知 Request DTO + * + * @author 芋道源码 + */ +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class PayOrderNotifyReqDTO { + + /** + * 商户订单编号 + */ + @NotEmpty(message = "商户订单号不能为空") + private String merchantOrderId; + + /** + * 支付订单编号 + */ + @NotNull(message = "支付订单编号不能为空") + private Long payOrderId; + +} diff --git a/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/api/notify/dto/PayRefundNotifyReqDTO.java b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/api/notify/dto/PayRefundNotifyReqDTO.java new file mode 100644 index 0000000..6746c31 --- /dev/null +++ b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/api/notify/dto/PayRefundNotifyReqDTO.java @@ -0,0 +1,40 @@ +package cn.iocoder.yudao.module.pay.api.notify.dto; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; + +/** + * 退款单的通知 Request DTO + * + * @author 芋道源码 + */ +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class PayRefundNotifyReqDTO { + + /** + * 商户退款单编号 + */ + @NotEmpty(message = "商户退款单编号不能为空") + private String merchantOrderId; + + /** + * 商户退款编号 + */ + @NotEmpty(message = "商户退款编号不能为空") + private String merchantRefundId; + + /** + * 支付退款编号 + */ + @NotNull(message = "支付退款编号不能为空") + private Long payRefundId; + +} diff --git a/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/api/notify/dto/PayTransferNotifyReqDTO.java b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/api/notify/dto/PayTransferNotifyReqDTO.java new file mode 100644 index 0000000..f3a92d8 --- /dev/null +++ b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/api/notify/dto/PayTransferNotifyReqDTO.java @@ -0,0 +1,34 @@ +package cn.iocoder.yudao.module.pay.api.notify.dto; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; + +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import lombok.NoArgsConstructor; + +/** + * 转账单的通知 Request DTO + * + * @author jason + */ +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class PayTransferNotifyReqDTO { + + /** + * 商户转账单号 + */ + @NotEmpty(message = "商户转账单号不能为空") + private String merchantTransferId; + + /** + * 转账订单编号 + */ + @NotNull(message = "转账订单编号不能为空") + private Long payTransferId; + +} diff --git a/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/api/notify/package-info.java b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/api/notify/package-info.java new file mode 100644 index 0000000..60c4ca8 --- /dev/null +++ b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/api/notify/package-info.java @@ -0,0 +1,4 @@ +/** + * 占位符,无特殊作用 + */ +package cn.iocoder.yudao.module.pay.api.notify; diff --git a/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/api/order/PayOrderApi.java b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/api/order/PayOrderApi.java new file mode 100644 index 0000000..965d79a --- /dev/null +++ b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/api/order/PayOrderApi.java @@ -0,0 +1,40 @@ +package cn.iocoder.yudao.module.pay.api.order; + +import cn.iocoder.yudao.module.pay.api.order.dto.PayOrderCreateReqDTO; +import cn.iocoder.yudao.module.pay.api.order.dto.PayOrderRespDTO; + +import jakarta.validation.Valid; + +/** + * 支付单 API 接口 + * + * @author LeeYan9 + * @since 2022-08-26 + */ +public interface PayOrderApi { + + /** + * 创建支付单 + * + * @param reqDTO 创建请求 + * @return 支付单编号 + */ + Long createOrder(@Valid PayOrderCreateReqDTO reqDTO); + + /** + * 获得支付单 + * + * @param id 支付单编号 + * @return 支付单 + */ + PayOrderRespDTO getOrder(Long id); + + /** + * 更新支付订单价格 + * + * @param id 支付单编号 + * @param payPrice 支付单价格 + */ + void updatePayOrderPrice(Long id, Integer payPrice); + +} diff --git a/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/api/order/PayOrderApiImpl.java b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/api/order/PayOrderApiImpl.java new file mode 100644 index 0000000..3584f47 --- /dev/null +++ b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/api/order/PayOrderApiImpl.java @@ -0,0 +1,39 @@ +package cn.iocoder.yudao.module.pay.api.order; + +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.convert.order.PayOrderConvert; +import cn.iocoder.yudao.module.pay.dal.dataobject.order.PayOrderDO; +import cn.iocoder.yudao.module.pay.service.order.PayOrderService; +import org.springframework.stereotype.Service; + +import jakarta.annotation.Resource; + +/** + * 支付单 API 实现类 + * + * @author 芋道源码 + */ +@Service +public class PayOrderApiImpl implements PayOrderApi { + + @Resource + private PayOrderService payOrderService; + + @Override + public Long createOrder(PayOrderCreateReqDTO reqDTO) { + return payOrderService.createOrder(reqDTO); + } + + @Override + public PayOrderRespDTO getOrder(Long id) { + PayOrderDO order = payOrderService.getOrder(id); + return PayOrderConvert.INSTANCE.convert2(order); + } + + @Override + public void updatePayOrderPrice(Long id, Integer payPrice) { + payOrderService.updatePayOrderPrice(id, payPrice); + } + +} diff --git a/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/api/order/dto/PayOrderCreateReqDTO.java b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/api/order/dto/PayOrderCreateReqDTO.java new file mode 100644 index 0000000..3a7b181 --- /dev/null +++ b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/api/order/dto/PayOrderCreateReqDTO.java @@ -0,0 +1,65 @@ +package cn.iocoder.yudao.module.pay.api.order.dto; + +import jakarta.validation.constraints.DecimalMin; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import lombok.Data; +import org.hibernate.validator.constraints.Length; + +import java.io.Serializable; +import java.time.LocalDateTime; + +/** + * 支付单创建 Request DTO + */ +@Data +public class PayOrderCreateReqDTO implements Serializable { + + public static final int SUBJECT_MAX_LENGTH = 32; + + /** + * 应用标识 + */ + @NotNull(message = "应用标识不能为空") + private String appKey; + /** + * 用户 IP + */ + @NotEmpty(message = "用户 IP 不能为空") + private String userIp; + + // ========== 商户相关字段 ========== + + /** + * 商户订单编号 + */ + @NotEmpty(message = "商户订单编号不能为空") + private String merchantOrderId; + /** + * 商品标题 + */ + @NotEmpty(message = "商品标题不能为空") + @Length(max = SUBJECT_MAX_LENGTH, message = "商品标题不能超过 32") + private String subject; + /** + * 商品描述 + */ + @Length(max = 128, message = "商品描述信息长度不能超过128") + private String body; + + // ========== 订单相关字段 ========== + + /** + * 支付金额,单位:分 + */ + @NotNull(message = "支付金额不能为空") + @DecimalMin(value = "0", inclusive = false, message = "支付金额必须大于零") + private Integer price; + + /** + * 支付过期时间 + */ + @NotNull(message = "支付过期时间不能为空") + private LocalDateTime expireTime; + +} diff --git a/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/api/order/dto/PayOrderRespDTO.java b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/api/order/dto/PayOrderRespDTO.java new file mode 100644 index 0000000..1287ce7 --- /dev/null +++ b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/api/order/dto/PayOrderRespDTO.java @@ -0,0 +1,68 @@ +package cn.iocoder.yudao.module.pay.api.order.dto; + +import cn.iocoder.yudao.module.pay.enums.order.PayOrderStatusEnum; +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * 支付单信息 Response DTO + * + * @author 芋道源码 + */ +@Data +public class PayOrderRespDTO { + + /** + * 订单编号,数据库自增 + */ + private Long id; + /** + * 渠道编码 + * + * 枚举 PayChannelEnum + */ + private String channelCode; + + // ========== 商户相关字段 ========== + /** + * 商户订单编号 + * 例如说,内部系统 A 的订单号。需要保证每个 PayMerchantDO 唯一 + */ + private String merchantOrderId; + + // ========== 订单相关字段 ========== + /** + * 商品标题 + */ + private String subject; + /** + * 支付金额,单位:分 + */ + private Integer price; + /** + * 支付状态 + * + * 枚举 {@link PayOrderStatusEnum} + */ + private Integer status; + + /** + * 订单支付成功时间 + */ + private LocalDateTime successTime; + + // ========== 渠道相关字段 ========== + + /** + * 渠道用户编号 + * + * 例如说,微信 openid、支付宝账号 + */ + private String channelUserId; + /** + * 渠道订单号 + */ + private String channelOrderNo; + +} diff --git a/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/api/package-info.java b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/api/package-info.java new file mode 100644 index 0000000..256747c --- /dev/null +++ b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/api/package-info.java @@ -0,0 +1,4 @@ +/** + * pay API 包,定义并实现提供给其它模块的 API + */ +package cn.iocoder.yudao.module.pay.api; diff --git a/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/api/refund/PayRefundApi.java b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/api/refund/PayRefundApi.java new file mode 100644 index 0000000..fa768f8 --- /dev/null +++ b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/api/refund/PayRefundApi.java @@ -0,0 +1,31 @@ +package cn.iocoder.yudao.module.pay.api.refund; + +import cn.iocoder.yudao.module.pay.api.refund.dto.PayRefundCreateReqDTO; +import cn.iocoder.yudao.module.pay.api.refund.dto.PayRefundRespDTO; + +import jakarta.validation.Valid; + +/** + * 退款单 API 接口 + * + * @author 芋道源码 + */ +public interface PayRefundApi { + + /** + * 创建退款单 + * + * @param reqDTO 创建请求 + * @return 退款单编号 + */ + Long createRefund(@Valid PayRefundCreateReqDTO reqDTO); + + /** + * 获得退款单 + * + * @param id 退款单编号 + * @return 退款单 + */ + PayRefundRespDTO getRefund(Long id); + +} diff --git a/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/api/refund/PayRefundApiImpl.java b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/api/refund/PayRefundApiImpl.java new file mode 100644 index 0000000..ccc538b --- /dev/null +++ b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/api/refund/PayRefundApiImpl.java @@ -0,0 +1,35 @@ +package cn.iocoder.yudao.module.pay.api.refund; + +import cn.iocoder.yudao.framework.common.util.object.BeanUtils; +import cn.iocoder.yudao.module.pay.api.refund.dto.PayRefundCreateReqDTO; +import cn.iocoder.yudao.module.pay.api.refund.dto.PayRefundRespDTO; +import cn.iocoder.yudao.module.pay.dal.dataobject.refund.PayRefundDO; +import cn.iocoder.yudao.module.pay.service.refund.PayRefundService; +import jakarta.annotation.Resource; +import org.springframework.stereotype.Service; +import org.springframework.validation.annotation.Validated; + +/** + * 退款单 API 实现类 + * + * @author 芋道源码 + */ +@Service +@Validated +public class PayRefundApiImpl implements PayRefundApi { + + @Resource + private PayRefundService payRefundService; + + @Override + public Long createRefund(PayRefundCreateReqDTO reqDTO) { + return payRefundService.createRefund(reqDTO); + } + + @Override + public PayRefundRespDTO getRefund(Long id) { + PayRefundDO refund = payRefundService.getRefund(id); + return BeanUtils.toBean(refund, PayRefundRespDTO.class); + } + +} diff --git a/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/api/refund/dto/PayRefundCreateReqDTO.java b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/api/refund/dto/PayRefundCreateReqDTO.java new file mode 100644 index 0000000..6910fc2 --- /dev/null +++ b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/api/refund/dto/PayRefundCreateReqDTO.java @@ -0,0 +1,57 @@ +package cn.iocoder.yudao.module.pay.api.refund.dto; + +import jakarta.validation.constraints.Min; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import lombok.Data; +import org.hibernate.validator.constraints.Length; + +/** + * 退款单创建 Request DTO + * + * @author 芋道源码 + */ +@Data +public class PayRefundCreateReqDTO { + + /** + * 应用标识 + */ + @NotNull(message = "应用标识不能为空") + private String appKey; + /** + * 用户 IP + */ + @NotEmpty(message = "用户 IP 不能为空") + private String userIp; + + // ========== 商户相关字段 ========== + /** + * 商户订单编号 + */ + @NotEmpty(message = "商户订单编号不能为空") + private String merchantOrderId; + + /** + * 商户退款编号 + */ + @NotEmpty(message = "商户退款编号不能为空") + private String merchantRefundId; + + /** + * 退款描述 + */ + @NotEmpty(message = "退款描述不能为空") + @Length(max = 128, message = "退款描述长度不能超过 128") + private String reason; + + // ========== 订单相关字段 ========== + + /** + * 退款金额,单位:分 + */ + @NotNull(message = "退款金额不能为空") + @Min(value = 1, message = "退款金额必须大于零") + private Integer price; + +} diff --git a/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/api/refund/dto/PayRefundRespDTO.java b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/api/refund/dto/PayRefundRespDTO.java new file mode 100644 index 0000000..725f391 --- /dev/null +++ b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/api/refund/dto/PayRefundRespDTO.java @@ -0,0 +1,65 @@ +package cn.iocoder.yudao.module.pay.api.refund.dto; + +import cn.iocoder.yudao.module.pay.enums.refund.PayRefundStatusEnum; +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * 退款单信息 Response DTO + * + * @author 芋道源码 + */ +@Data +public class PayRefundRespDTO { + + /** + * 退款单编号 + */ + private Long id; + + /** + * 渠道编码 + * + * 枚举 PayChannelEnum + */ + private String channelCode; + + // ========== 退款相关字段 ========== + /** + * 退款状态 + * + * 枚举 {@link PayRefundStatusEnum} + */ + private Integer status; + /** + * 退款金额,单位:分 + */ + private Integer refundPrice; + + // ========== 商户相关字段 ========== + /** + * 商户订单编号 + */ + private String merchantOrderId; + /** + * 商户退款编号 + */ + private String merchantRefundId; + /** + * 退款成功时间 + */ + private LocalDateTime successTime; + + // ========== 渠道相关字段 ========== + + /** + * 调用渠道的错误码 + */ + private String channelErrorCode; + /** + * 调用渠道的错误提示 + */ + private String channelErrorMsg; + +} diff --git a/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/api/transfer/PayTransferApi.java b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/api/transfer/PayTransferApi.java new file mode 100644 index 0000000..e3227eb --- /dev/null +++ b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/api/transfer/PayTransferApi.java @@ -0,0 +1,31 @@ +package cn.iocoder.yudao.module.pay.api.transfer; + +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; +import jakarta.validation.Valid; + +/** + * 转账单 API 接口 + * + * @author jason + */ +public interface PayTransferApi { + + /** + * 创建转账单 + * + * @param reqDTO 创建请求 + * @return 创建结果 + */ + PayTransferCreateRespDTO createTransfer(@Valid PayTransferCreateReqDTO reqDTO); + + /** + * 获得转账单 + * + * @param id 转账单编号 + * @return 转账单 + */ + PayTransferRespDTO getTransfer(Long id); + +} diff --git a/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/api/transfer/PayTransferApiImpl.java b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/api/transfer/PayTransferApiImpl.java new file mode 100644 index 0000000..6c6ea06 --- /dev/null +++ b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/api/transfer/PayTransferApiImpl.java @@ -0,0 +1,49 @@ +package cn.iocoder.yudao.module.pay.api.transfer; + +import cn.iocoder.yudao.framework.common.util.object.BeanUtils; +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; +import cn.iocoder.yudao.module.pay.dal.dataobject.channel.PayChannelDO; +import cn.iocoder.yudao.module.pay.dal.dataobject.transfer.PayTransferDO; +import cn.iocoder.yudao.module.pay.service.channel.PayChannelService; +import cn.iocoder.yudao.module.pay.service.transfer.PayTransferService; +import jakarta.annotation.Resource; +import org.springframework.stereotype.Service; +import org.springframework.validation.annotation.Validated; + +/** + * 转账单 API 实现类 + * + * @author jason + */ +@Service +@Validated +public class PayTransferApiImpl implements PayTransferApi { + + @Resource + private PayTransferService payTransferService; + @Resource + private PayChannelService payChannelService; + + @Override + public PayTransferCreateRespDTO createTransfer(PayTransferCreateReqDTO reqDTO) { + return payTransferService.createTransfer(reqDTO); + } + + @Override + public PayTransferRespDTO getTransfer(Long id) { + PayTransferDO transfer = payTransferService.getTransfer(id); + if (transfer == null) { + return null; + } + PayChannelDO channel = payChannelService.getChannel(transfer.getChannelId()); + String mchId = null; + if (channel != null && channel.getConfig() instanceof WxPayClientConfig) { + mchId = ((WxPayClientConfig) channel.getConfig()).getMchId(); + } + return BeanUtils.toBean(transfer, PayTransferRespDTO.class).setChannelMchId(mchId); + } + +} diff --git a/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/api/transfer/dto/PayTransferCreateReqDTO.java b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/api/transfer/dto/PayTransferCreateReqDTO.java new file mode 100644 index 0000000..1ac5c04 --- /dev/null +++ b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/api/transfer/dto/PayTransferCreateReqDTO.java @@ -0,0 +1,120 @@ +package cn.iocoder.yudao.module.pay.api.transfer.dto; + +import cn.hutool.core.util.StrUtil; +import cn.iocoder.yudao.framework.common.util.json.JsonUtils; +import jakarta.validation.constraints.Min; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * 转账单创建 Request DTO + * + * @author jason + */ +@Data +public class PayTransferCreateReqDTO { + + /** + * 应用标识 + */ + @NotNull(message = "应用标识不能为空") + private String appKey; + + /** + * 转账渠道 + */ + @NotEmpty(message = "转账渠道不能为空") + private String channelCode; + + /** + * 转账渠道的额外参数 + */ + private Map channelExtras; + + /** + * 用户 IP + */ + @NotEmpty(message = "用户 IP 不能为空") + private String userIp; + + /** + * 商户转账单编号 + */ + @NotEmpty(message = "商户转账单编号能为空") + private String merchantTransferId; + + /** + * 转账金额,单位:分 + */ + @Min(value = 1, message = "转账金额必须大于零") + @NotNull(message = "转账金额不能为空") + private Integer price; + + /** + * 转账标题 + */ + @NotEmpty(message = "转账标题不能为空") + private String subject; + + /** + * 收款人账号 + * + * 微信场景下:openid + * 支付宝场景下:支付宝账号 + */ + @NotEmpty(message = "收款人账号不能为空") + private String userAccount; + /** + * 收款人姓名 + */ + private String userName; + + /** + * 【微信】现金营销场景 + * + * @param activityName 活动名称 + * @param rewardDescription 奖励说明 + * @return channelExtras + */ + public static Map buildWeiXinChannelExtra1000(String activityName, String rewardDescription) { + return buildWeiXinChannelExtra(1000, + "活动名称", activityName, + "奖励说明", rewardDescription); + } + + /** + * 【微信】企业报销场景 + * + * @param expenseType 报销类型 + * @param expenseDescription 报销说明 + * @return channelExtras + */ + public static Map buildWeiXinChannelExtra1006(String expenseType, String expenseDescription) { + return buildWeiXinChannelExtra(1006, + "报销类型", expenseType, + "报销说明", expenseDescription); + } + + private static Map buildWeiXinChannelExtra(Integer sceneId, String... values) { + Map channelExtras = new HashMap<>(); + // 构建场景报备信息列表 + List> sceneReportInfos = new ArrayList<>(); + for (int i = 0; i < values.length; i += 2) { + Map info = new HashMap<>(); + info.put("infoType", values[i]); + info.put("infoContent", values[i + 1]); + sceneReportInfos.add(info); + } + // 设置场景ID和场景报备信息 + channelExtras.put("sceneId", StrUtil.toString(sceneId)); + channelExtras.put("sceneReportInfos", JsonUtils.toJsonString(sceneReportInfos)); + return channelExtras; + } + +} diff --git a/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/api/transfer/dto/PayTransferCreateRespDTO.java b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/api/transfer/dto/PayTransferCreateRespDTO.java new file mode 100644 index 0000000..a445eb0 --- /dev/null +++ b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/api/transfer/dto/PayTransferCreateRespDTO.java @@ -0,0 +1,28 @@ +package cn.iocoder.yudao.module.pay.api.transfer.dto; + +import lombok.Data; + +/** + * 转账单创建 Response DTO + * + * @author 芋道源码 + */ +@Data +public class PayTransferCreateRespDTO { + + /** + * 编号 + */ + private Long id; + + // ========== 其它字段 ========== + + /** + * 渠道 package 信息 + * + * 特殊:目前只有微信转账有这个东西!!! + * @see JSAPI 调起用户确认收款 + */ + private String channelPackageInfo; + +} diff --git a/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/api/transfer/dto/PayTransferRespDTO.java b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/api/transfer/dto/PayTransferRespDTO.java new file mode 100644 index 0000000..52126ce --- /dev/null +++ b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/api/transfer/dto/PayTransferRespDTO.java @@ -0,0 +1,81 @@ +package cn.iocoder.yudao.module.pay.api.transfer.dto; + +import cn.iocoder.yudao.module.pay.enums.PayChannelEnum; +import cn.iocoder.yudao.module.pay.enums.transfer.PayTransferStatusEnum; +import lombok.Data; + +import java.time.LocalDateTime; + +@Data +public class PayTransferRespDTO { + + /** + * 编号 + */ + private Long id; + + /** + * 转账单号 + */ + private String no; + + /** + * 转账渠道编码 + * + * 枚举 {@link PayChannelEnum} + */ + private String channelCode; + + // ========== 商户相关字段 ========== + + /** + * 商户转账单编号 + */ + private String merchantTransferId; + + // ========== 转账相关字段 ========== + + /** + * 转账金额,单位:分 + */ + private Integer price; + + /** + * 转账状态 + * + * 枚举 {@link PayTransferStatusEnum} + */ + private Integer status; + + /** + * 订单转账成功时间 + */ + private LocalDateTime successTime; + + // ========== 其它字段 ========== + + /** + * 调用渠道的错误码 + */ + private String channelErrorCode; + /** + * 调用渠道的错误提示 + */ + private String channelErrorMsg; + + /** + * 渠道 package 信息 + * + * 特殊:目前只有微信转账有这个东西!!! + * @see JSAPI 调起用户确认收款 + */ + private String channelPackageInfo; + /** + * 渠道商户号 + * + * 特殊:目前只有微信转账有这个东西!!! + * @see JSAPI 调起用户确认收款 + */ + private String channelMchId; + +} diff --git a/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/api/wallet/PayWalletApi.java b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/api/wallet/PayWalletApi.java new file mode 100644 index 0000000..5586929 --- /dev/null +++ b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/api/wallet/PayWalletApi.java @@ -0,0 +1,29 @@ +package cn.iocoder.yudao.module.pay.api.wallet; + +import cn.iocoder.yudao.module.pay.api.wallet.dto.PayWalletAddBalanceReqDTO; +import cn.iocoder.yudao.module.pay.api.wallet.dto.PayWalletRespDTO; + +/** + * 钱包 API 接口 + * + * @author liurulin + */ +public interface PayWalletApi { + + /** + * 添加钱包余额 + * + * @param reqDTO 增加余额请求 + */ + void addWalletBalance(PayWalletAddBalanceReqDTO reqDTO); + + /** + * 获取钱包信息 + * + * @param userId 用户编号 + * @param userType 用户类型 + * @return 钱包信息 + */ + PayWalletRespDTO getOrCreateWallet(Long userId, Integer userType); + +} diff --git a/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/api/wallet/PayWalletApiImpl.java b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/api/wallet/PayWalletApiImpl.java new file mode 100644 index 0000000..627cb5c --- /dev/null +++ b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/api/wallet/PayWalletApiImpl.java @@ -0,0 +1,41 @@ +package cn.iocoder.yudao.module.pay.api.wallet; + +import cn.hutool.core.lang.Assert; +import cn.iocoder.yudao.framework.common.util.object.BeanUtils; +import cn.iocoder.yudao.module.pay.api.wallet.dto.PayWalletAddBalanceReqDTO; +import cn.iocoder.yudao.module.pay.api.wallet.dto.PayWalletRespDTO; +import cn.iocoder.yudao.module.pay.dal.dataobject.wallet.PayWalletDO; +import cn.iocoder.yudao.module.pay.enums.wallet.PayWalletBizTypeEnum; +import cn.iocoder.yudao.module.pay.service.wallet.PayWalletService; +import jakarta.annotation.Resource; +import org.springframework.stereotype.Service; + +/** + * 钱包 API 实现类 + * + * @author 芋道源码 + */ +@Service +public class PayWalletApiImpl implements PayWalletApi { + + @Resource + private PayWalletService payWalletService; + + @Override + public void addWalletBalance(PayWalletAddBalanceReqDTO reqDTO) { + // 创建或获取钱包 + PayWalletDO wallet = payWalletService.getOrCreateWallet(reqDTO.getUserId(), reqDTO.getUserType()); + Assert.notNull(wallet, "钱包({}/{})不存在", reqDTO.getUserId(), reqDTO.getUserType()); + + // 增加余额 + PayWalletBizTypeEnum bizType = PayWalletBizTypeEnum.valueOf(reqDTO.getBizType()); + payWalletService.addWalletBalance(wallet.getId(), reqDTO.getBizId(), bizType, reqDTO.getPrice()); + } + + @Override + public PayWalletRespDTO getOrCreateWallet(Long userId, Integer userType) { + PayWalletDO wallet = payWalletService.getOrCreateWallet(userId, userType); + return BeanUtils.toBean(wallet, PayWalletRespDTO.class); + } + +} diff --git a/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/api/wallet/dto/PayWalletAddBalanceReqDTO.java b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/api/wallet/dto/PayWalletAddBalanceReqDTO.java new file mode 100644 index 0000000..264649d --- /dev/null +++ b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/api/wallet/dto/PayWalletAddBalanceReqDTO.java @@ -0,0 +1,49 @@ +package cn.iocoder.yudao.module.pay.api.wallet.dto; + +import cn.iocoder.yudao.framework.common.enums.UserTypeEnum; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +/** + * 钱包余额增加 Request DTO + * + * @author 芋道源码 + */ +@Data +public class PayWalletAddBalanceReqDTO { + + /** + * 用户编号 + * + * 关联 MemberUserDO 的 id 属性,或者 AdminUserDO 的 id 属性 + */ + @NotNull(message = "用户编号不能为空") + private Long userId; + /** + * 用户类型 + * + * 关联 {@link UserTypeEnum} + */ + @NotNull(message = "用户类型不能为空") + private Integer userType; + + /** + * 关联业务分类 + */ + @NotNull(message = "关联业务分类不能为空") + private Integer bizType; + /** + * 关联业务编号 + */ + @NotNull(message = "关联业务编号不能为空") + private String bizId; + + /** + * 交易金额,单位分 + * + * 正值表示余额增加,负值表示余额减少 + */ + @NotNull(message = "交易金额不能为空") + private Integer price; + +} diff --git a/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/api/wallet/dto/PayWalletRespDTO.java b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/api/wallet/dto/PayWalletRespDTO.java new file mode 100644 index 0000000..9a8db79 --- /dev/null +++ b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/api/wallet/dto/PayWalletRespDTO.java @@ -0,0 +1,52 @@ +package cn.iocoder.yudao.module.pay.api.wallet.dto; + +import cn.iocoder.yudao.framework.common.enums.UserTypeEnum; +import lombok.Data; + +/** + * 钱包 Response DTO + * + * @author jason + */ +@Data +public class PayWalletRespDTO { + + /** + * 编号 + */ + private Long id; + + /** + * 用户 id + * + * 关联 MemberUserDO 的 id 编号 + * 关联 AdminUserDO 的 id 编号 + */ + private Long userId; + /** + * 用户类型, 预留 多商户转帐可能需要用到 + * + * 关联 {@link UserTypeEnum} + */ + private Integer userType; + + /** + * 余额,单位分 + */ + private Integer balance; + + /** + * 冻结金额,单位分 + */ + private Integer freezePrice; + + /** + * 累计支出,单位分 + */ + private Integer totalExpense; + /** + * 累计充值,单位分 + */ + private Integer totalRecharge; + +} diff --git a/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/app/PayAppController.java b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/app/PayAppController.java new file mode 100644 index 0000000..a3e6d28 --- /dev/null +++ b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/app/PayAppController.java @@ -0,0 +1,108 @@ +package cn.iocoder.yudao.module.pay.controller.admin.app; + +import cn.hutool.core.collection.CollUtil; +import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; +import cn.iocoder.yudao.framework.common.pojo.CommonResult; +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.module.pay.controller.admin.app.vo.*; +import cn.iocoder.yudao.module.pay.convert.app.PayAppConvert; +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.service.app.PayAppService; +import cn.iocoder.yudao.module.pay.service.channel.PayChannelService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.annotation.Resource; +import jakarta.validation.Valid; +import lombok.extern.slf4j.Slf4j; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList; + +@Slf4j +@Tag(name = "管理后台 - 支付应用信息") +@RestController +@RequestMapping("/pay/app") +@Validated +public class PayAppController { + + @Resource + private PayAppService appService; + @Resource + private PayChannelService channelService; + + @PostMapping("/create") + @Operation(summary = "创建支付应用信息") + @PreAuthorize("@ss.hasPermission('pay:app:create')") + public CommonResult createApp(@Valid @RequestBody PayAppCreateReqVO createReqVO) { + return success(appService.createApp(createReqVO)); + } + + @PutMapping("/update") + @Operation(summary = "更新支付应用信息") + @PreAuthorize("@ss.hasPermission('pay:app:update')") + public CommonResult updateApp(@Valid @RequestBody PayAppUpdateReqVO updateReqVO) { + appService.updateApp(updateReqVO); + return success(true); + } + + @PutMapping("/update-status") + @Operation(summary = "更新支付应用状态") + @PreAuthorize("@ss.hasPermission('pay:app:update')") + public CommonResult updateAppStatus(@Valid @RequestBody PayAppUpdateStatusReqVO updateReqVO) { + appService.updateAppStatus(updateReqVO.getId(), updateReqVO.getStatus()); + return success(true); + } + + @DeleteMapping("/delete") + @Operation(summary = "删除支付应用信息") + @Parameter(name = "id", description = "编号", required = true) + @PreAuthorize("@ss.hasPermission('pay:app:delete')") + public CommonResult deleteApp(@RequestParam("id") Long id) { + appService.deleteApp(id); + return success(true); + } + + @GetMapping("/get") + @Operation(summary = "获得支付应用信息") + @Parameter(name = "id", description = "编号", required = true, example = "1024") + @PreAuthorize("@ss.hasPermission('pay:app:query')") + public CommonResult getApp(@RequestParam("id") Long id) { + PayAppDO app = appService.getApp(id); + return success(PayAppConvert.INSTANCE.convert(app)); + } + + @GetMapping("/page") + @Operation(summary = "获得支付应用信息分页") + @PreAuthorize("@ss.hasPermission('pay:app:query')") + public CommonResult> getAppPage(@Valid PayAppPageReqVO pageVO) { + // 得到应用分页列表 + PageResult pageResult = appService.getAppPage(pageVO); + if (CollUtil.isEmpty(pageResult.getList())) { + return success(PageResult.empty()); + } + + // 得到所有的应用编号,查出所有的渠道,并移除未启用的渠道 + List channels = channelService.getChannelListByAppIds( + convertList(pageResult.getList(), PayAppDO::getId)); + channels.removeIf(channel -> !CommonStatusEnum.ENABLE.getStatus().equals(channel.getStatus())); + + // 拼接后返回 + return success(PayAppConvert.INSTANCE.convertPage(pageResult, channels)); + } + + @GetMapping("/list") + @Operation(summary = "获得应用列表") + @PreAuthorize("@ss.hasPermission('pay:merchant:query')") + public CommonResult> getAppList() { + List appListDO = appService.getAppList(); + return success(PayAppConvert.INSTANCE.convertList(appListDO)); + } + +} diff --git a/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/app/vo/PayAppBaseVO.java b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/app/vo/PayAppBaseVO.java new file mode 100644 index 0000000..4fad3d1 --- /dev/null +++ b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/app/vo/PayAppBaseVO.java @@ -0,0 +1,47 @@ +package cn.iocoder.yudao.module.pay.controller.admin.app.vo; +import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; +import cn.iocoder.yudao.framework.common.validation.InEnum; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.*; +import org.hibernate.validator.constraints.URL; + +import jakarta.validation.constraints.*; + +/** +* 支付应用信息 Base VO,提供给添加、修改、详细的子 VO 使用 +* 如果子 VO 存在差异的字段,请不要添加到这里,影响 Swagger 文档生成 +*/ +@Data +public class PayAppBaseVO { + + @Schema(description = "应用标识", requiredMode = Schema.RequiredMode.REQUIRED, example = "yudao") + @NotEmpty(message = "应用标识不能为空") + private String appKey; + + @Schema(description = "应用名", requiredMode = Schema.RequiredMode.REQUIRED, example = "小豆") + @NotNull(message = "应用名不能为空") + private String name; + + @Schema(description = "开启状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "0") + @NotNull(message = "开启状态不能为空") + @InEnum(CommonStatusEnum.class) + private Integer status; + + @Schema(description = "备注", example = "我是一个测试应用") + private String remark; + + @Schema(description = "支付结果的回调地址", requiredMode = Schema.RequiredMode.REQUIRED, example = "http://127.0.0.1:48080/pay-callback") + @NotNull(message = "支付结果的回调地址不能为空") + @URL(message = "支付结果的回调地址必须为 URL 格式") + private String orderNotifyUrl; + + @Schema(description = "退款结果的回调地址", requiredMode = Schema.RequiredMode.REQUIRED, example = "http://127.0.0.1:48080/refund-callback") + @NotNull(message = "退款结果的回调地址不能为空") + @URL(message = "退款结果的回调地址必须为 URL 格式") + private String refundNotifyUrl; + + @Schema(description = "转账结果的回调地址", example = "http://127.0.0.1:48080/transfer-callback") + @URL(message = "转账结果的回调地址必须为 URL 格式") + private String transferNotifyUrl; + +} diff --git a/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/app/vo/PayAppCreateReqVO.java b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/app/vo/PayAppCreateReqVO.java new file mode 100644 index 0000000..f1a5ddd --- /dev/null +++ b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/app/vo/PayAppCreateReqVO.java @@ -0,0 +1,14 @@ +package cn.iocoder.yudao.module.pay.controller.admin.app.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; + +@Schema(description = "管理后台 - 支付应用信息创建 Request VO") +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class PayAppCreateReqVO extends PayAppBaseVO { + +} diff --git a/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/app/vo/PayAppPageItemRespVO.java b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/app/vo/PayAppPageItemRespVO.java new file mode 100644 index 0000000..76b6200 --- /dev/null +++ b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/app/vo/PayAppPageItemRespVO.java @@ -0,0 +1,26 @@ +package cn.iocoder.yudao.module.pay.controller.admin.app.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; + +import java.time.LocalDateTime; +import java.util.Set; + +@Schema(description = "管理后台 - 支付应用信息分页查询 Response VO,相比于支付信息,还会多出应用渠道的开关信息") +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class PayAppPageItemRespVO extends PayAppBaseVO { + + @Schema(description = "应用编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") + private Long id; + + @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED) + private LocalDateTime createTime; + + @Schema(description = "已配置的支付渠道编码", requiredMode = Schema.RequiredMode.REQUIRED, example = "[alipay_pc, alipay_wap]") + private Set channelCodes; + +} diff --git a/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/app/vo/PayAppPageReqVO.java b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/app/vo/PayAppPageReqVO.java new file mode 100644 index 0000000..e433c85 --- /dev/null +++ b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/app/vo/PayAppPageReqVO.java @@ -0,0 +1,33 @@ +package cn.iocoder.yudao.module.pay.controller.admin.app.vo; + +import cn.iocoder.yudao.framework.common.pojo.PageParam; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; +import org.springframework.format.annotation.DateTimeFormat; + +import java.time.LocalDateTime; + +import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND; + +@Schema(description = "管理后台 - 支付应用信息分页 Request VO") +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class PayAppPageReqVO extends PageParam { + + @Schema(description = "应用名", example = "小豆") + private String name; + + @Schema(description = "应用标识", example = "yudao") + private String appKey; + + @Schema(description = "开启状态", example = "0") + private Integer status; + + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) + @Schema(description = "创建时间") + private LocalDateTime[] createTime; + +} diff --git a/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/app/vo/PayAppRespVO.java b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/app/vo/PayAppRespVO.java new file mode 100644 index 0000000..184e538 --- /dev/null +++ b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/app/vo/PayAppRespVO.java @@ -0,0 +1,25 @@ +package cn.iocoder.yudao.module.pay.controller.admin.app.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; + +import java.time.LocalDateTime; + +@Schema(description = "管理后台 - 支付应用信息 Response VO") +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class PayAppRespVO extends PayAppBaseVO { + + @Schema(description = "应用编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") + private Long id; + + @Schema(description = "应用标识", requiredMode = Schema.RequiredMode.REQUIRED, example = "yudao") + private String appKey; + + @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED) + private LocalDateTime createTime; + +} diff --git a/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/app/vo/PayAppUpdateReqVO.java b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/app/vo/PayAppUpdateReqVO.java new file mode 100644 index 0000000..c4e50bd --- /dev/null +++ b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/app/vo/PayAppUpdateReqVO.java @@ -0,0 +1,17 @@ +package cn.iocoder.yudao.module.pay.controller.admin.app.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.*; +import jakarta.validation.constraints.*; + +@Schema(description = "管理后台 - 支付应用信息更新 Request VO") +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class PayAppUpdateReqVO extends PayAppBaseVO { + + @Schema(description = "应用编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") + @NotNull(message = "应用编号不能为空") + private Long id; + +} diff --git a/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/app/vo/PayAppUpdateStatusReqVO.java b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/app/vo/PayAppUpdateStatusReqVO.java new file mode 100644 index 0000000..585a18f --- /dev/null +++ b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/app/vo/PayAppUpdateStatusReqVO.java @@ -0,0 +1,20 @@ +package cn.iocoder.yudao.module.pay.controller.admin.app.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import jakarta.validation.constraints.NotNull; + +@Schema(description = "管理后台 - 应用更新状态 Request VO") +@Data +public class PayAppUpdateStatusReqVO { + + @Schema(description = "应用编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") + @NotNull(message = "应用编号不能为空") + private Long id; + + @Schema(description = "状态,见 SysCommonStatusEnum 枚举", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + @NotNull(message = "状态不能为空") + private Integer status; + +} diff --git a/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/channel/PayChannelController.java b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/channel/PayChannelController.java new file mode 100644 index 0000000..b7b0aed --- /dev/null +++ b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/channel/PayChannelController.java @@ -0,0 +1,82 @@ +package cn.iocoder.yudao.module.pay.controller.admin.channel; + +import cn.iocoder.yudao.framework.common.pojo.CommonResult; +import cn.iocoder.yudao.module.pay.controller.admin.channel.vo.PayChannelCreateReqVO; +import cn.iocoder.yudao.module.pay.controller.admin.channel.vo.PayChannelRespVO; +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.service.channel.PayChannelService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.tags.Tag; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import jakarta.annotation.Resource; +import jakarta.validation.Valid; +import java.util.List; +import java.util.Set; + +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet; + +@Tag(name = "管理后台 - 支付渠道") +@RestController +@RequestMapping("/pay/channel") +@Validated +public class PayChannelController { + + @Resource + private PayChannelService channelService; + + @PostMapping("/create") + @Operation(summary = "创建支付渠道 ") + @PreAuthorize("@ss.hasPermission('pay:channel:create')") + public CommonResult createChannel(@Valid @RequestBody PayChannelCreateReqVO createReqVO) { + return success(channelService.createChannel(createReqVO)); + } + + @PutMapping("/update") + @Operation(summary = "更新支付渠道 ") + @PreAuthorize("@ss.hasPermission('pay:channel:update')") + public CommonResult updateChannel(@Valid @RequestBody PayChannelUpdateReqVO updateReqVO) { + channelService.updateChannel(updateReqVO); + return success(true); + } + + @DeleteMapping("/delete") + @Operation(summary = "删除支付渠道 ") + @Parameter(name = "id", description = "编号", required = true) + @PreAuthorize("@ss.hasPermission('pay:channel:delete')") + public CommonResult deleteChannel(@RequestParam("id") Long id) { + channelService.deleteChannel(id); + return success(true); + } + + @GetMapping("/get") + @Operation(summary = "获得支付渠道") + @Parameter(name = "id", description = "编号", required = true, example = "1024") + @PreAuthorize("@ss.hasPermission('pay:channel:query')") + public CommonResult getChannel(@RequestParam(value = "id", required = false) Long id, + @RequestParam(value = "appId", required = false) Long appId, + @RequestParam(value = "code", required = false) String code) { + PayChannelDO channel = null; + if (id != null) { + channel = channelService.getChannel(id); + } else if (appId != null && code != null) { + channel = channelService.getChannelByAppIdAndCode(appId, code); + } + return success(PayChannelConvert.INSTANCE.convert(channel)); + } + + @GetMapping("/get-enable-code-list") + @Operation(summary = "获得指定应用的开启的支付渠道编码列表") + @Parameter(name = "appId", description = "应用编号", required = true, example = "1") + public CommonResult> getEnableChannelCodeList(@RequestParam("appId") Long appId) { + List channels = channelService.getEnableChannelList(appId); + return success(convertSet(channels, PayChannelDO::getCode)); + } + +} diff --git a/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/channel/vo/PayChannelBaseVO.java b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/channel/vo/PayChannelBaseVO.java new file mode 100644 index 0000000..df99a3c --- /dev/null +++ b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/channel/vo/PayChannelBaseVO.java @@ -0,0 +1,31 @@ +package cn.iocoder.yudao.module.pay.controller.admin.channel.vo; +import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; +import cn.iocoder.yudao.framework.common.validation.InEnum; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.*; +import jakarta.validation.constraints.*; + +/** +* 支付渠道 Base VO,提供给添加、修改、详细的子 VO 使用 +* 如果子 VO 存在差异的字段,请不要添加到这里,影响 Swagger 文档生成 +*/ +@Data +public class PayChannelBaseVO { + + @Schema(description = "开启状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + @NotNull(message = "开启状态不能为空") + @InEnum(CommonStatusEnum.class) + private Integer status; + + @Schema(description = "备注", example = "我是小备注") + private String remark; + + @Schema(description = "渠道费率,单位:百分比", requiredMode = Schema.RequiredMode.REQUIRED, example = "10") + @NotNull(message = "渠道费率,单位:百分比不能为空") + private Double feeRate; + + @Schema(description = "应用编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") + @NotNull(message = "应用编号不能为空") + private Long appId; + +} diff --git a/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/channel/vo/PayChannelCreateReqVO.java b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/channel/vo/PayChannelCreateReqVO.java new file mode 100644 index 0000000..ccb0497 --- /dev/null +++ b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/channel/vo/PayChannelCreateReqVO.java @@ -0,0 +1,25 @@ +package cn.iocoder.yudao.module.pay.controller.admin.channel.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; + +@Schema(description = "管理后台 - 支付渠道 创建 Request VO") +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class PayChannelCreateReqVO extends PayChannelBaseVO { + + @Schema(description = "渠道编码", requiredMode = Schema.RequiredMode.REQUIRED, example = "alipay_pc") + @NotNull(message = "渠道编码不能为空") + private String code; + + @Schema(description = "渠道配置的 json 字符串") + @NotBlank(message = "渠道配置不能为空") + private String config; + +} diff --git a/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/channel/vo/PayChannelRespVO.java b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/channel/vo/PayChannelRespVO.java new file mode 100644 index 0000000..dafd29e --- /dev/null +++ b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/channel/vo/PayChannelRespVO.java @@ -0,0 +1,25 @@ +package cn.iocoder.yudao.module.pay.controller.admin.channel.vo; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.*; + +import java.time.LocalDateTime; + +@Schema(description = "管理后台 - 支付渠道 Response VO") +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class PayChannelRespVO extends PayChannelBaseVO { + + @Schema(description = "商户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") + private Long id; + + @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") + private LocalDateTime createTime; + + @Schema(description = "渠道编码", requiredMode = Schema.RequiredMode.REQUIRED, example = "alipay_pc") + private String code; + + @Schema(description = "配置", requiredMode = Schema.RequiredMode.REQUIRED) + private String config; + +} diff --git a/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/channel/vo/PayChannelUpdateReqVO.java b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/channel/vo/PayChannelUpdateReqVO.java new file mode 100644 index 0000000..9998528 --- /dev/null +++ b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/channel/vo/PayChannelUpdateReqVO.java @@ -0,0 +1,20 @@ +package cn.iocoder.yudao.module.pay.controller.admin.channel.vo; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.*; +import jakarta.validation.constraints.*; + +@Schema(description = "管理后台 - 支付渠道 更新 Request VO") +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class PayChannelUpdateReqVO extends PayChannelBaseVO { + + @Schema(description = "商户编号", requiredMode = Schema.RequiredMode.REQUIRED) + @NotNull(message = "商户编号不能为空") + private Long id; + + @Schema(description = "渠道配置的json字符串") + @NotBlank(message = "渠道配置不能为空") + private String config; + +} diff --git a/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/demo/PayDemoOrderController.java b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/demo/PayDemoOrderController.java new file mode 100644 index 0000000..f571d88 --- /dev/null +++ b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/demo/PayDemoOrderController.java @@ -0,0 +1,76 @@ +package cn.iocoder.yudao.module.pay.controller.admin.demo; + +import cn.iocoder.yudao.framework.common.pojo.CommonResult; +import cn.iocoder.yudao.framework.common.pojo.PageParam; +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.common.util.object.BeanUtils; +import cn.iocoder.yudao.module.pay.api.notify.dto.PayOrderNotifyReqDTO; +import cn.iocoder.yudao.module.pay.api.notify.dto.PayRefundNotifyReqDTO; +import cn.iocoder.yudao.module.pay.controller.admin.demo.vo.order.PayDemoOrderCreateReqVO; +import cn.iocoder.yudao.module.pay.controller.admin.demo.vo.order.PayDemoOrderRespVO; +import cn.iocoder.yudao.module.pay.dal.dataobject.demo.PayDemoOrderDO; +import cn.iocoder.yudao.module.pay.service.demo.PayDemoOrderService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.annotation.Resource; +import jakarta.annotation.security.PermitAll; +import jakarta.validation.Valid; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; +import static cn.iocoder.yudao.framework.common.util.servlet.ServletUtils.getClientIP; +import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId; + +@Tag(name = "管理后台 - 示例订单") // 目的:演示支付、退款功能 +@RestController +@RequestMapping("/pay/demo-order") +@Validated +public class PayDemoOrderController { + + @Resource + private PayDemoOrderService payDemoOrderService; + + @PostMapping("/create") + @Operation(summary = "创建示例订单") + public CommonResult createDemoOrder(@Valid @RequestBody PayDemoOrderCreateReqVO createReqVO) { + return success(payDemoOrderService.createDemoOrder(getLoginUserId(), createReqVO)); + } + + @GetMapping("/page") + @Operation(summary = "获得示例订单分页") + public CommonResult> getDemoOrderPage(@Valid PageParam pageVO) { + PageResult pageResult = payDemoOrderService.getDemoOrderPage(pageVO); + return success(BeanUtils.toBean(pageResult, PayDemoOrderRespVO.class)); + } + + @PostMapping("/update-paid") + @Operation(summary = "更新示例订单为已支付") // 由 pay-module 支付服务,进行回调,可见 PayNotifyJob + @PermitAll // 无需登录,安全由 PayDemoOrderService 内部校验实现 + public CommonResult updateDemoOrderPaid(@RequestBody PayOrderNotifyReqDTO notifyReqDTO) { + payDemoOrderService.updateDemoOrderPaid(Long.valueOf(notifyReqDTO.getMerchantOrderId()), + notifyReqDTO.getPayOrderId()); + return success(true); + } + + @PutMapping("/refund") + @Operation(summary = "发起示例订单的退款") + @Parameter(name = "id", description = "编号", required = true, example = "1024") + public CommonResult refundDemoOrder(@RequestParam("id") Long id) { + payDemoOrderService.refundDemoOrder(id, getClientIP()); + return success(true); + } + + @PostMapping("/update-refunded") + @Operation(summary = "更新示例订单为已退款") // 由 pay-module 支付服务,进行回调,可见 PayNotifyJob + @PermitAll // 无需登录,安全由 PayDemoOrderService 内部校验实现 + public CommonResult updateDemoOrderRefunded(@RequestBody PayRefundNotifyReqDTO notifyReqDTO) { + payDemoOrderService.updateDemoOrderRefunded( + Long.valueOf(notifyReqDTO.getMerchantOrderId()), + notifyReqDTO.getMerchantRefundId(), + notifyReqDTO.getPayRefundId()); + return success(true); + } + +} diff --git a/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/demo/PayDemoWithdrawController.http b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/demo/PayDemoWithdrawController.http new file mode 100644 index 0000000..4162baa --- /dev/null +++ b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/demo/PayDemoWithdrawController.http @@ -0,0 +1,50 @@ +### 请求 /pay/demo-withdraw/create 接口(支付宝) +POST {{baseUrl}}/pay/demo-withdraw/create +Authorization: Bearer {{token}} +Content-Type: application/json +tenant-id: {{adminTenantId}} + +{ + "type": 1, + "subject": "测试转账", + "price": 10, + "userAccount": "oespxk7368@sandbox.com", + "userName": "oespxk7368" +} + +### 请求 /pay/demo-withdraw/create 接口(微信余额) +POST {{baseUrl}}/pay/demo-withdraw/create +Authorization: Bearer {{token}} +Content-Type: application/json +tenant-id: {{adminTenantId}} + +{ + "type": 2, + "subject": "测试转账", + "price": 1, + "userAccount": "oiSC85elO_OZogXODC5RoGyXamK4", + "userName": "芋艿" +} + +### 请求 /pay/demo-withdraw/create 接口(钱包余额) +POST {{baseUrl}}/pay/demo-withdraw/create +Authorization: Bearer {{token}} +Content-Type: application/json +tenant-id: {{adminTenantId}} + +{ + "type": 3, + "subject": "测试转账", + "price": 1, + "userAccount": "1" +} + +### 请求 /pay/demo-withdraw/transfer 接口(发起转账) +POST {{baseUrl}}/pay/demo-withdraw/transfer?id=1 +Authorization: Bearer {{token}} +tenant-id: {{adminTenantId}} + +### 请求 /pay/demo-withdraw/page 接口(查询分页) +GET {{baseUrl}}/pay/demo-withdraw/page?pageNo=1&pageSize=10 +Authorization: Bearer {{token}} +tenant-id: {{adminTenantId}} \ No newline at end of file diff --git a/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/demo/PayDemoWithdrawController.java b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/demo/PayDemoWithdrawController.java new file mode 100644 index 0000000..033bc95 --- /dev/null +++ b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/demo/PayDemoWithdrawController.java @@ -0,0 +1,63 @@ +package cn.iocoder.yudao.module.pay.controller.admin.demo; + +import cn.iocoder.yudao.framework.common.pojo.CommonResult; +import cn.iocoder.yudao.framework.common.pojo.PageParam; +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.common.util.object.BeanUtils; +import cn.iocoder.yudao.module.pay.api.notify.dto.PayTransferNotifyReqDTO; +import cn.iocoder.yudao.module.pay.controller.admin.demo.vo.withdraw.PayDemoWithdrawCreateReqVO; +import cn.iocoder.yudao.module.pay.controller.admin.demo.vo.withdraw.PayDemoWithdrawRespVO; +import cn.iocoder.yudao.module.pay.dal.dataobject.demo.PayDemoWithdrawDO; +import cn.iocoder.yudao.module.pay.service.demo.PayDemoWithdrawService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.annotation.Resource; +import jakarta.annotation.security.PermitAll; +import jakarta.validation.Valid; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; + +@Tag(name = "管理后台 - 示例提现订单") // 目的:演示转账功能 +@RestController +@RequestMapping("/pay/demo-withdraw") +@Validated +public class PayDemoWithdrawController { + + @Resource + private PayDemoWithdrawService demoWithdrawService; + + @PostMapping("/create") + @Operation(summary = "创建示例提现单") + public CommonResult createDemoWithdraw(@Valid @RequestBody PayDemoWithdrawCreateReqVO createReqVO) { + Long id = demoWithdrawService.createDemoWithdraw(createReqVO); + return success(id); + } + + @PostMapping("/transfer") + @Operation(summary = "提现单转账") + @Parameter(name = "id", required = true, description = "提现单编号", example = "1024") + public CommonResult transferDemoWithdraw(@RequestParam("id") Long id) { + Long payTransferId = demoWithdrawService.transferDemoWithdraw(id); + return success(payTransferId); + } + + @GetMapping("/page") + @Operation(summary = "获得示例提现单分页") + public CommonResult> getDemoWithdrawPage(@Valid PageParam pageVO) { + PageResult pageResult = demoWithdrawService.getDemoWithdrawPage(pageVO); + return success(BeanUtils.toBean(pageResult, PayDemoWithdrawRespVO.class)); + } + + @PostMapping("/update-transferred") + @Operation(summary = "更新示例提现单的转账状态") // 由 pay-module 转账服务,进行回调 + @PermitAll // 无需登录,安全由 PayDemoTransferService 内部校验实现 + public CommonResult updateDemoWithdrawTransferred(@RequestBody PayTransferNotifyReqDTO notifyReqDTO) { + demoWithdrawService.updateDemoWithdrawTransferred(Long.valueOf(notifyReqDTO.getMerchantTransferId()), + notifyReqDTO.getPayTransferId()); + return success(true); + } + +} diff --git a/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/demo/vo/order/PayDemoOrderCreateReqVO.java b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/demo/vo/order/PayDemoOrderCreateReqVO.java new file mode 100644 index 0000000..27ca07b --- /dev/null +++ b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/demo/vo/order/PayDemoOrderCreateReqVO.java @@ -0,0 +1,16 @@ +package cn.iocoder.yudao.module.pay.controller.admin.demo.vo.order; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import jakarta.validation.constraints.NotNull; + +@Schema(description = "管理后台 - 示例订单创建 Request VO") +@Data +public class PayDemoOrderCreateReqVO { + + @Schema(description = "商品编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "17682") + @NotNull(message = "商品编号不能为空") + private Long spuId; + +} diff --git a/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/demo/vo/order/PayDemoOrderRespVO.java b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/demo/vo/order/PayDemoOrderRespVO.java new file mode 100644 index 0000000..cb30563 --- /dev/null +++ b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/demo/vo/order/PayDemoOrderRespVO.java @@ -0,0 +1,54 @@ +package cn.iocoder.yudao.module.pay.controller.admin.demo.vo.order; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.*; + +import java.time.LocalDateTime; + +/** +* 示例订单 Base VO,提供给添加、修改、详细的子 VO 使用 +* 如果子 VO 存在差异的字段,请不要添加到这里,影响 Swagger 文档生成 +*/ +@Data +public class PayDemoOrderRespVO { + + @Schema(description = "订单编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") + private Long id; + + @Schema(description = "用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "23199") + private Long userId; + + @Schema(description = "商品编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "17682") + private Long spuId; + + @Schema(description = "商家备注", example = "李四") + private String spuName; + + @Schema(description = "价格,单位:分", requiredMode = Schema.RequiredMode.REQUIRED, example = "30381") + private Integer price; + + @Schema(description = "是否已支付", requiredMode = Schema.RequiredMode.REQUIRED) + private Boolean payStatus; + + @Schema(description = "支付订单编号", example = "16863") + private Long payOrderId; + + @Schema(description = "订单支付时间") + private LocalDateTime payTime; + + @Schema(description = "支付渠道", example = "alipay_qr") + private String payChannelCode; + + @Schema(description = "支付退款编号", example = "23366") + private Long payRefundId; + + @Schema(description = "退款金额,单位:分", requiredMode = Schema.RequiredMode.REQUIRED, example = "14039") + private Integer refundPrice; + + @Schema(description = "退款时间") + private LocalDateTime refundTime; + + @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED) + private LocalDateTime createTime; + +} diff --git a/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/demo/vo/withdraw/PayDemoWithdrawCreateReqVO.java b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/demo/vo/withdraw/PayDemoWithdrawCreateReqVO.java new file mode 100644 index 0000000..3c6c1c8 --- /dev/null +++ b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/demo/vo/withdraw/PayDemoWithdrawCreateReqVO.java @@ -0,0 +1,42 @@ +package cn.iocoder.yudao.module.pay.controller.admin.demo.vo.withdraw; + +import cn.hutool.core.util.ObjectUtil; +import cn.iocoder.yudao.framework.common.validation.InEnum; +import cn.iocoder.yudao.module.pay.enums.demo.PayDemoWithdrawTypeEnum; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.*; +import lombok.Data; + +@Schema(description = "管理后台 - 示例提现单创建 Request VO") +@Data +public class PayDemoWithdrawCreateReqVO { + + @Schema(description = "提现标题", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋艿是一种菜") + @NotEmpty(message = "提现标题不能为空") + private String subject; + + @Schema(description = "提现金额,单位:分", requiredMode = Schema.RequiredMode.REQUIRED, example = "100") + @NotNull(message = "提现金额不能为空") + @Min(value = 1, message = "提现金额必须大于零") + private Integer price; + + @Schema(description = "收款人账号", requiredMode= Schema.RequiredMode.REQUIRED, example = "test1") + @NotBlank(message = "收款人账号不能为空") + private String userAccount; + + @Schema(description = "收款人姓名", example = "test1") + private String userName; + + @Schema(description = "提现方式", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + @NotNull(message = "提现方式不能为空") + @InEnum(PayDemoWithdrawTypeEnum.class) + private Integer type; + + @AssertTrue(message = "收款人姓名") + public boolean isUserNameValid() { + // 特殊:支付宝必须填写用户名!!! + return ObjectUtil.notEqual(type, PayDemoWithdrawTypeEnum.ALIPAY.getType()) + || ObjectUtil.isNotEmpty(userName); + } + +} diff --git a/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/demo/vo/withdraw/PayDemoWithdrawRespVO.java b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/demo/vo/withdraw/PayDemoWithdrawRespVO.java new file mode 100644 index 0000000..3abc607 --- /dev/null +++ b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/demo/vo/withdraw/PayDemoWithdrawRespVO.java @@ -0,0 +1,47 @@ +package cn.iocoder.yudao.module.pay.controller.admin.demo.vo.withdraw; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.time.LocalDateTime; + +@Schema(description = "管理后台 - 示例转账单创建 Request VO") +@Data +public class PayDemoWithdrawRespVO { + + @Schema(description = "转账单编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") + private Long id; + + @Schema(description = "提现标题", requiredMode = Schema.RequiredMode.REQUIRED, example = "吃饭报销") + private String subject; + + @Schema(description = "提现金额,单位:分", requiredMode = Schema.RequiredMode.REQUIRED, example = "22338") + private Integer price; + + @Schema(description = "收款人姓名", example = "test") + private String userName; + + @Schema(description = "收款人账号", example = "32167") + private String userAccount; + + @Schema(description = "提现类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + private Integer type; + + @Schema(description = "提现状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "2") + private Integer status; + + // ========== 转账相关字段 ========== + + @Schema(description = "转账单编号", example = "23695") + private Long payTransferId; + + @Schema(description = "转账渠道", example = "wx_lite") + private String transferChannelCode; + + @Schema(description = "转账成功时间") + private LocalDateTime transferTime; + + @Schema(description = "转账失败原因", example = "IP 不正确") + private String transferErrorMsg; + +} diff --git a/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/notify/PayNotifyController.java b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/notify/PayNotifyController.java new file mode 100644 index 0000000..bcf97ea --- /dev/null +++ b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/notify/PayNotifyController.java @@ -0,0 +1,169 @@ +package cn.iocoder.yudao.module.pay.controller.admin.notify; + +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.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; +import cn.iocoder.yudao.module.pay.controller.admin.notify.vo.PayNotifyTaskRespVO; +import cn.iocoder.yudao.module.pay.dal.dataobject.app.PayAppDO; +import cn.iocoder.yudao.module.pay.dal.dataobject.notify.PayNotifyLogDO; +import cn.iocoder.yudao.module.pay.dal.dataobject.notify.PayNotifyTaskDO; +import cn.iocoder.yudao.module.pay.service.app.PayAppService; +import cn.iocoder.yudao.module.pay.service.channel.PayChannelService; +import cn.iocoder.yudao.module.pay.service.notify.PayNotifyService; +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 io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.extern.slf4j.Slf4j; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import jakarta.annotation.Resource; +import jakarta.annotation.security.PermitAll; +import jakarta.validation.Valid; +import java.util.List; +import java.util.Map; + +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList; +import static cn.iocoder.yudao.module.pay.enums.ErrorCodeConstants.CHANNEL_NOT_FOUND; + +@Tag(name = "管理后台 - 回调通知") +@RestController +@RequestMapping("/pay/notify") +@Validated +@Slf4j +public class PayNotifyController { + + @Resource + private PayOrderService orderService; + @Resource + private PayRefundService refundService; + @Resource + private PayTransferService payTransferService; + @Resource + private PayNotifyService notifyService; + @Resource + private PayAppService appService; + @Resource + private PayChannelService channelService; + + @PostMapping(value = "/order/{channelId}") + @Operation(summary = "支付渠道的统一【支付】回调") + @PermitAll + @TenantIgnore + public String notifyOrder(@PathVariable("channelId") Long channelId, + @RequestParam(required = false) Map params, + @RequestBody(required = false) String body, + @RequestHeader Map headers) { + log.info("[notifyOrder][channelId({}) 回调数据({}/{})]", channelId, params, body); + // 1. 校验支付渠道是否存在 + PayClient payClient = channelService.getPayClient(channelId); + if (payClient == null) { + log.error("[notifyOrder][渠道编号({}) 找不到对应的支付客户端]", channelId); + throw exception(CHANNEL_NOT_FOUND); + } + + // 2. 解析通知数据 + PayOrderRespDTO notify = payClient.parseOrderNotify(params, body, headers); + orderService.notifyOrder(channelId, notify); + return "success"; + } + + @PostMapping(value = "/refund/{channelId}") + @Operation(summary = "支付渠道的统一【退款】回调") + @PermitAll + @TenantIgnore + public String notifyRefund(@PathVariable("channelId") Long channelId, + @RequestParam(required = false) Map params, + @RequestBody(required = false) String body, + @RequestHeader Map headers) { + log.info("[notifyRefund][channelId({}) 回调数据({}/{})]", channelId, params, body); + // 1. 校验支付渠道是否存在 + PayClient payClient = channelService.getPayClient(channelId); + if (payClient == null) { + log.error("[notifyRefund][渠道编号({}) 找不到对应的支付客户端]", channelId); + throw exception(CHANNEL_NOT_FOUND); + } + + // 2. 解析通知数据 + PayRefundRespDTO notify = payClient.parseRefundNotify(params, body, headers); + refundService.notifyRefund(channelId, notify); + return "success"; + } + + @PostMapping(value = "/transfer/{channelId}") + @Operation(summary = "支付渠道的统一【转账】回调") + @PermitAll + @TenantIgnore + public String notifyTransfer(@PathVariable("channelId") Long channelId, + @RequestParam(required = false) Map params, + @RequestBody(required = false) String body, + @RequestHeader Map headers) { + log.info("[notifyTransfer][channelId({}) 回调数据({}/{})]", channelId, params, body); + // 1. 校验支付渠道是否存在 + PayClient payClient = channelService.getPayClient(channelId); + if (payClient == null) { + log.error("[notifyTransfer][渠道编号({}) 找不到对应的支付客户端]", channelId); + throw exception(CHANNEL_NOT_FOUND); + } + + // 2. 解析通知数据 + PayTransferRespDTO notify = payClient.parseTransferNotify(params, body, headers); + payTransferService.notifyTransfer(channelId, notify); + return "success"; + } + + @GetMapping("/get-detail") + @Operation(summary = "获得回调通知的明细") + @Parameter(name = "id", description = "编号", required = true, example = "1024") + @PreAuthorize("@ss.hasPermission('pay:notify:query')") + public CommonResult getNotifyTaskDetail(@RequestParam("id") Long id) { + PayNotifyTaskDO task = notifyService.getNotifyTask(id); + if (task == null) { + return success(null); + } + // 拼接返回 + PayAppDO app = appService.getApp(task.getAppId()); + List logs = notifyService.getNotifyLogList(id); + return success(BeanUtils.toBean(task, PayNotifyTaskDetailRespVO.class, respVO -> { + if (app != null) { + respVO.setAppName(app.getName()); + } + respVO.setLogs(BeanUtils.toBean(logs, PayNotifyTaskDetailRespVO.Log.class)); + })); + } + + @GetMapping("/page") + @Operation(summary = "获得回调通知分页") + @PreAuthorize("@ss.hasPermission('pay:notify:query')") + public CommonResult> getNotifyTaskPage(@Valid PayNotifyTaskPageReqVO pageVO) { + PageResult pageResult = notifyService.getNotifyTaskPage(pageVO); + if (CollUtil.isEmpty(pageResult.getList())) { + return success(PageResult.empty()); + } + // 拼接返回 + Map apps = appService.getAppMap(convertList(pageResult.getList(), PayNotifyTaskDO::getAppId)); + + // 转换对象 + return success(BeanUtils.toBean(pageResult, PayNotifyTaskRespVO.class, order -> { + PayAppDO app = apps.get(order.getAppId()); + if (app != null) { + order.setAppName(app.getName()); + } + })); + } + +} diff --git a/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/notify/vo/PayNotifyTaskDetailRespVO.java b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/notify/vo/PayNotifyTaskDetailRespVO.java new file mode 100644 index 0000000..739cedf --- /dev/null +++ b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/notify/vo/PayNotifyTaskDetailRespVO.java @@ -0,0 +1,42 @@ + +package cn.iocoder.yudao.module.pay.controller.admin.notify.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; + +import java.time.LocalDateTime; +import java.util.List; + +@Schema(description = "管理后台 - 回调通知的明细 Response VO") +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class PayNotifyTaskDetailRespVO extends PayNotifyTaskRespVO { + + @Schema(description = "回调日志列表") + private List logs; + + @Schema(description = "管理后台 - 回调日志") + @Data + public static class Log { + + @Schema(description = "日志编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "8848") + private Long id; + + @Schema(description = "通知状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + private Byte status; + + @Schema(description = "当前通知次数", requiredMode = Schema.RequiredMode.REQUIRED) + private Byte notifyTimes; + + @Schema(description = "HTTP 响应结果", requiredMode = Schema.RequiredMode.REQUIRED) + private String response; + + @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED) + private LocalDateTime createTime; + + } + +} diff --git a/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/notify/vo/PayNotifyTaskPageReqVO.java b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/notify/vo/PayNotifyTaskPageReqVO.java new file mode 100644 index 0000000..f6dbc84 --- /dev/null +++ b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/notify/vo/PayNotifyTaskPageReqVO.java @@ -0,0 +1,45 @@ +package cn.iocoder.yudao.module.pay.controller.admin.notify.vo; + +import cn.iocoder.yudao.framework.common.pojo.PageParam; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; +import org.springframework.format.annotation.DateTimeFormat; + +import java.time.LocalDateTime; + +import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND; + +@Schema(description = "管理后台 - 回调通知分页 Request VO") +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class PayNotifyTaskPageReqVO extends PageParam { + + @Schema(description = "应用编号", example = "10636") + private Long appId; + + @Schema(description = "通知类型", example = "2") + private Integer type; + + @Schema(description = "数据编号", example = "6722") + private Long dataId; + + @Schema(description = "通知状态", example = "1") + private Integer status; + + @Schema(description = "商户订单编号", example = "26697") + private String merchantOrderId; + + @Schema(description = "商户退款编号", example = "26697") + private String merchantRefundId; + + @Schema(description = "商户转账编号", example = "26697") + private String merchantTransferId; + + @Schema(description = "创建时间") + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) + private LocalDateTime[] createTime; + +} diff --git a/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/notify/vo/PayNotifyTaskRespVO.java b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/notify/vo/PayNotifyTaskRespVO.java new file mode 100644 index 0000000..75228b1 --- /dev/null +++ b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/notify/vo/PayNotifyTaskRespVO.java @@ -0,0 +1,56 @@ +package cn.iocoder.yudao.module.pay.controller.admin.notify.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.*; +import java.time.LocalDateTime; + +@Schema(description = "管理后台 - 回调通知 Response VO") +@Data +public class PayNotifyTaskRespVO { + + @Schema(description = "任务编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "3380") + private Long id; + + @Schema(description = "应用编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "10636") + private Long appId; + + @Schema(description = "应用名称", example = "wx_pay") + private String appName; + + @Schema(description = "通知类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "2") + private Byte type; + + @Schema(description = "数据编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "6722") + private Long dataId; + + @Schema(description = "商户订单编号", example = "26697") + private String merchantOrderId; + + @Schema(description = "商户退款编号", example = "26697") + private String merchantRefundId; + + @Schema(description = "商户转账编号", example = "26697") + private String merchantTransferId; + + @Schema(description = "通知状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + private Byte status; + + @Schema(description = "下一次通知时间", requiredMode = Schema.RequiredMode.REQUIRED) + private LocalDateTime nextNotifyTime; + + @Schema(description = "最后一次执行时间", requiredMode = Schema.RequiredMode.REQUIRED) + private LocalDateTime lastExecuteTime; + + @Schema(description = "当前通知次数", requiredMode = Schema.RequiredMode.REQUIRED) + private Byte notifyTimes; + + @Schema(description = "最大可通知次数", requiredMode = Schema.RequiredMode.REQUIRED) + private Byte maxNotifyTimes; + + @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED) + private LocalDateTime createTime; + + @Schema(description = "更新时间", requiredMode = Schema.RequiredMode.REQUIRED) + private LocalDateTime updateTime; + +} diff --git a/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/order/PayOrderController.java b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/order/PayOrderController.java new file mode 100755 index 0000000..97bd29f --- /dev/null +++ b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/order/PayOrderController.java @@ -0,0 +1,145 @@ +package cn.iocoder.yudao.module.pay.controller.admin.order; + +import cn.hutool.core.collection.CollectionUtil; +import cn.iocoder.yudao.framework.apilog.core.annotation.ApiAccessLog; +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.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.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; +import com.google.common.collect.Maps; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.Parameters; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.annotation.Resource; +import jakarta.servlet.http.HttpServletResponse; +import jakarta.validation.Valid; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Objects; + +import static cn.iocoder.yudao.framework.apilog.core.enums.OperateTypeEnum.EXPORT; +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList; +import static cn.iocoder.yudao.framework.common.util.servlet.ServletUtils.getClientIP; +import static cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils.getLoginUserId; +import static cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils.getLoginUserType; + +@Tag(name = "管理后台 - 支付订单") +@RestController +@RequestMapping("/pay/order") +@Validated +public class PayOrderController { + + @Resource + private PayOrderService orderService; + @Resource + private PayAppService appService; + @Resource + private PayWalletService payWalletService; + + @GetMapping("/get") + @Operation(summary = "获得支付订单") + @Parameters({ + @Parameter(name = "id", description = "编号", required = true, example = "1024"), + @Parameter(name = "sync", description = "是否同步", example = "true") + }) + @PreAuthorize("@ss.hasPermission('pay:order:query')") + public CommonResult getOrder(@RequestParam("id") Long id, + @RequestParam(value = "sync", required = false) Boolean sync) { + PayOrderDO order = orderService.getOrder(id); + // sync 仅在等待支付 + if (Boolean.TRUE.equals(sync) && PayOrderStatusEnum.isWaiting(order.getStatus())) { + orderService.syncOrderQuietly(order.getId()); + // 重新查询,因为同步后,可能会有变化 + order = orderService.getOrder(id); + } + return success(BeanUtils.toBean(order, PayOrderRespVO.class)); + } + + @GetMapping("/get-detail") + @Operation(summary = "获得支付订单详情") + @Parameter(name = "id", description = "编号", required = true, example = "1024") + @PreAuthorize("@ss.hasPermission('pay:order:query')") + public CommonResult getOrderDetail(@RequestParam("id") Long id) { + PayOrderDO order = orderService.getOrder(id); + if (order == null) { + return success(null); + } + + // 拼接返回 + PayAppDO app = appService.getApp(order.getAppId()); + PayOrderExtensionDO orderExtension = orderService.getOrderExtension(order.getExtensionId()); + return success(PayOrderConvert.INSTANCE.convert(order, orderExtension, app)); + } + + @PostMapping("/submit") + @Operation(summary = "提交支付订单") + public CommonResult submitPayOrder(@RequestBody PayOrderSubmitReqVO reqVO) { + // 1. 钱包支付事,需要额外传 user_id 和 user_type + if (Objects.equals(reqVO.getChannelCode(), PayChannelEnum.WALLET.getCode())) { + if (reqVO.getChannelExtras() == null) { + reqVO.setChannelExtras(Maps.newHashMapWithExpectedSize(1)); + } + PayWalletDO wallet = payWalletService.getOrCreateWallet(getLoginUserId(), getLoginUserType()); + reqVO.getChannelExtras().put(WalletPayClient.WALLET_ID_KEY, String.valueOf(wallet.getId())); + } + + // 2. 提交支付 + PayOrderSubmitRespVO respVO = orderService.submitOrder(reqVO, getClientIP()); + return success(respVO); + } + + @GetMapping("/page") + @Operation(summary = "获得支付订单分页") + @PreAuthorize("@ss.hasPermission('pay:order:query')") + public CommonResult> getOrderPage(@Valid PayOrderPageReqVO pageVO) { + PageResult pageResult = orderService.getOrderPage(pageVO); + if (CollectionUtil.isEmpty(pageResult.getList())) { + return success(new PageResult<>(pageResult.getTotal())); + } + + // 拼接返回 + Map appMap = appService.getAppMap(convertList(pageResult.getList(), PayOrderDO::getAppId)); + return success(PayOrderConvert.INSTANCE.convertPage(pageResult, appMap)); + } + + @GetMapping("/export-excel") + @Operation(summary = "导出支付订单 Excel") + @PreAuthorize("@ss.hasPermission('pay:order:export')") + @ApiAccessLog(operateType = EXPORT) + public void exportOrderExcel(@Valid PayOrderExportReqVO exportReqVO, + HttpServletResponse response) throws IOException { + List list = orderService.getOrderList(exportReqVO); + if (CollectionUtil.isEmpty(list)) { + ExcelUtils.write(response, "支付订单.xls", "数据", + PayOrderExcelVO.class, new ArrayList<>()); + return; + } + + // 拼接返回 + Map appMap = appService.getAppMap(convertList(list, PayOrderDO::getAppId)); + List excelList = PayOrderConvert.INSTANCE.convertList(list, appMap); + // 导出 Excel + ExcelUtils.write(response, "支付订单.xls", "数据", PayOrderExcelVO.class, excelList); + } + +} diff --git a/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/order/vo/PayOrderBaseVO.java b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/order/vo/PayOrderBaseVO.java new file mode 100755 index 0000000..8cb5b80 --- /dev/null +++ b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/order/vo/PayOrderBaseVO.java @@ -0,0 +1,89 @@ +package cn.iocoder.yudao.module.pay.controller.admin.order.vo; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import org.springframework.format.annotation.DateTimeFormat; + +import jakarta.validation.constraints.NotNull; +import java.time.LocalDateTime; + +import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND; + +/** + * 支付订单 Base VO,提供给添加、修改、详细的子 VO 使用 + * 如果子 VO 存在差异的字段,请不要添加到这里,影响 Swagger 文档生成 + * + * @author aquan + */ +@Data +public class PayOrderBaseVO { + + @Schema(description = "应用编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") + @NotNull(message = "应用编号不能为空") + private Long appId; + + @Schema(description = "渠道编号", example = "2048") + private Long channelId; + + @Schema(description = "渠道编码", example = "wx_app") + private String channelCode; + + @Schema(description = "商户订单编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "888") + @NotNull(message = "商户订单编号不能为空") + private String merchantOrderId; + + @Schema(description = "商品标题", requiredMode = Schema.RequiredMode.REQUIRED, example = "土豆") + @NotNull(message = "商品标题不能为空") + private String subject; + + @Schema(description = "商品描述", requiredMode = Schema.RequiredMode.REQUIRED, example = "我是土豆") + @NotNull(message = "商品描述不能为空") + private String body; + + @Schema(description = "异步通知地址", requiredMode = Schema.RequiredMode.REQUIRED, example = "http://127.0.0.1:48080/pay/notify") + @NotNull(message = "异步通知地址不能为空") + private String notifyUrl; + + @Schema(description = "支付金额,单位:分", requiredMode = Schema.RequiredMode.REQUIRED, example = "10") + @NotNull(message = "支付金额,单位:分不能为空") + private Long price; + + @Schema(description = "渠道手续费,单位:百分比", example = "10") + private Double channelFeeRate; + + @Schema(description = "渠道手续金额,单位:分", example = "100") + private Integer channelFeePrice; + + @Schema(description = "支付状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + @NotNull(message = "支付状态不能为空") + private Integer status; + + @Schema(description = "用户 IP", requiredMode = Schema.RequiredMode.REQUIRED, example = "127.0.0.1") + @NotNull(message = "用户 IP不能为空") + private String userIp; + + @Schema(description = "订单失效时间", requiredMode = Schema.RequiredMode.REQUIRED) + @NotNull(message = "订单失效时间不能为空") + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) + private LocalDateTime expireTime; + + @Schema(description = "订单支付成功时间") + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) + private LocalDateTime successTime; + + @Schema(description = "支付成功的订单拓展单编号", example = "50") + private Long extensionId; + + @Schema(description = "支付订单号", example = "2048888") + private String no; + + @Schema(description = "退款总金额,单位:分", requiredMode = Schema.RequiredMode.REQUIRED, example = "10") + @NotNull(message = "退款总金额,单位:分不能为空") + private Long refundPrice; + + @Schema(description = "渠道用户编号", example = "2048") + private String channelUserId; + + @Schema(description = "渠道订单号", example = "4096") + private String channelOrderNo; + +} diff --git a/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/order/vo/PayOrderDetailsRespVO.java b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/order/vo/PayOrderDetailsRespVO.java new file mode 100644 index 0000000..a4fe463 --- /dev/null +++ b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/order/vo/PayOrderDetailsRespVO.java @@ -0,0 +1,45 @@ +package cn.iocoder.yudao.module.pay.controller.admin.order.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; + +import java.time.LocalDateTime; + +@Schema(description = "管理后台 - 支付订单详细信息 Response VO") +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class PayOrderDetailsRespVO extends PayOrderBaseVO { + + @Schema(description = "支付订单编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") + private Long id; + + @Schema(description = "应用名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋道源码") + private String appName; + + @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED) + private LocalDateTime createTime; + + @Schema(description = "更新时间", requiredMode = Schema.RequiredMode.REQUIRED) + private LocalDateTime updateTime; + + /** + * 支付订单扩展 + */ + private PayOrderExtension extension; + + @Data + @Schema(description = "支付订单扩展") + public static class PayOrderExtension { + + @Schema(description = "支付订单号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") + private String no; + + @Schema(description = "支付异步通知的内容") + private String channelNotifyData; + + } + +} diff --git a/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/order/vo/PayOrderExcelVO.java b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/order/vo/PayOrderExcelVO.java new file mode 100755 index 0000000..5dc17a0 --- /dev/null +++ b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/order/vo/PayOrderExcelVO.java @@ -0,0 +1,67 @@ +package cn.iocoder.yudao.module.pay.controller.admin.order.vo; + +import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat; +import cn.iocoder.yudao.framework.excel.core.convert.DictConvert; +import cn.iocoder.yudao.framework.excel.core.convert.MoneyConvert; +import cn.iocoder.yudao.module.pay.enums.DictTypeConstants; +import com.alibaba.excel.annotation.ExcelProperty; +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * 支付订单 Excel VO + * + * @author aquan + */ +@Data +public class PayOrderExcelVO { + + @ExcelProperty("编号") + private Long id; + + @ExcelProperty("创建时间") + private LocalDateTime createTime; + + @ExcelProperty(value = "支付金额", converter = MoneyConvert.class) + private Integer price; + + @ExcelProperty(value = "退款金额", converter = MoneyConvert.class) + private Integer refundPrice; + + @ExcelProperty(value = "手续金额", converter = MoneyConvert.class) + private Integer channelFeePrice; + + @ExcelProperty("商户单号") + private String merchantOrderId; + + @ExcelProperty(value = "支付单号") + private String no; + + @ExcelProperty("渠道单号") + private String channelOrderNo; + + @ExcelProperty(value = "支付状态", converter = DictConvert.class) + @DictFormat(DictTypeConstants.ORDER_STATUS) + private Integer status; + + @ExcelProperty(value = "渠道编号名称", converter = DictConvert.class) + @DictFormat(DictTypeConstants.CHANNEL_CODE) + private String channelCode; + + @ExcelProperty("订单支付成功时间") + private LocalDateTime successTime; + + @ExcelProperty("订单失效时间") + private LocalDateTime expireTime; + + @ExcelProperty(value = "应用名称") + private String appName; + + @ExcelProperty("商品标题") + private String subject; + + @ExcelProperty("商品描述") + private String body; + +} diff --git a/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/order/vo/PayOrderExportReqVO.java b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/order/vo/PayOrderExportReqVO.java new file mode 100755 index 0000000..9e4d20b --- /dev/null +++ b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/order/vo/PayOrderExportReqVO.java @@ -0,0 +1,37 @@ +package cn.iocoder.yudao.module.pay.controller.admin.order.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import org.springframework.format.annotation.DateTimeFormat; + +import java.time.LocalDateTime; + +import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND; + +@Schema(description = "管理后台 - 支付订单 Excel 导出 Request VO,参数和 PayOrderPageReqVO 是一致的") +@Data +public class PayOrderExportReqVO { + + @Schema(description = "应用编号", example = "1024") + private Long appId; + + @Schema(description = "渠道编码", example = "wx_app") + private String channelCode; + + @Schema(description = "商户订单编号", example = "4096") + private String merchantOrderId; + + @Schema(description = "渠道编号", example = "1888") + private String channelOrderNo; + + @Schema(description = "支付单号", example = "2014888") + private String no; + + @Schema(description = "支付状态", example = "0") + private Integer status; + + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) + @Schema(description = "创建时间") + private LocalDateTime[] createTime; + +} diff --git a/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/order/vo/PayOrderPageItemRespVO.java b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/order/vo/PayOrderPageItemRespVO.java new file mode 100755 index 0000000..05411c9 --- /dev/null +++ b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/order/vo/PayOrderPageItemRespVO.java @@ -0,0 +1,25 @@ +package cn.iocoder.yudao.module.pay.controller.admin.order.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; + +import java.time.LocalDateTime; + +@Schema(description = "管理后台 - 支付订单分页 Request VO") +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class PayOrderPageItemRespVO extends PayOrderBaseVO { + + @Schema(description = "支付订单编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") + private Long id; + + @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED) + private LocalDateTime createTime; + + @Schema(description = "应用名称", example = "wx_pay") + private String appName; + +} diff --git a/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/order/vo/PayOrderPageReqVO.java b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/order/vo/PayOrderPageReqVO.java new file mode 100755 index 0000000..f7ff801 --- /dev/null +++ b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/order/vo/PayOrderPageReqVO.java @@ -0,0 +1,42 @@ +package cn.iocoder.yudao.module.pay.controller.admin.order.vo; + +import cn.iocoder.yudao.framework.common.pojo.PageParam; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; +import org.springframework.format.annotation.DateTimeFormat; + +import java.time.LocalDateTime; + +import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND; + +@Schema(description = "管理后台 - 支付订单分页 Request VO") +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class PayOrderPageReqVO extends PageParam { + + @Schema(description = "应用编号", example = "1024") + private Long appId; + + @Schema(description = "渠道编码", example = "wx_app") + private String channelCode; + + @Schema(description = "商户订单编号", example = "4096") + private String merchantOrderId; + + @Schema(description = "渠道编号", example = "1888") + private String channelOrderNo; + + @Schema(description = "支付单号", example = "2014888") + private String no; + + @Schema(description = "支付状态", example = "0") + private Integer status; + + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) + @Schema(description = "创建时间") + private LocalDateTime[] createTime; + +} diff --git a/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/order/vo/PayOrderRespVO.java b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/order/vo/PayOrderRespVO.java new file mode 100644 index 0000000..d48a4db --- /dev/null +++ b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/order/vo/PayOrderRespVO.java @@ -0,0 +1,22 @@ +package cn.iocoder.yudao.module.pay.controller.admin.order.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; + +import java.time.LocalDateTime; + +@Schema(description = "管理后台 - 支付订单 Response VO") +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class PayOrderRespVO extends PayOrderBaseVO { + + @Schema(description = "支付订单编号", requiredMode = Schema.RequiredMode.REQUIRED) + private Long id; + + @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED) + private LocalDateTime createTime; + +} diff --git a/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/order/vo/PayOrderSubmitReqVO.java b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/order/vo/PayOrderSubmitReqVO.java new file mode 100644 index 0000000..3fc7fc9 --- /dev/null +++ b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/order/vo/PayOrderSubmitReqVO.java @@ -0,0 +1,33 @@ +package cn.iocoder.yudao.module.pay.controller.admin.order.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import org.hibernate.validator.constraints.URL; + +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import java.util.Map; + +@Schema(description = "管理后台 - 支付订单提交 Request VO") +@Data +public class PayOrderSubmitReqVO { + + @Schema(description = "支付单编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") + @NotNull(message = "支付单编号不能为空") + private Long id; + + @Schema(description = "支付渠道", requiredMode = Schema.RequiredMode.REQUIRED, example = "wx_pub") + @NotEmpty(message = "支付渠道不能为空") + private String channelCode; + + @Schema(description = "支付渠道的额外参数,例如说,微信公众号需要传递 openid 参数") + private Map channelExtras; + + @Schema(description = "展示模式", example = "url") // 参见 {@link PayDisplayModeEnum} 枚举。如果不传递,则每个支付渠道使用默认的方式 + private String displayMode; + + @Schema(description = "回跳地址") + @URL(message = "回跳地址的格式必须是 URL") + private String returnUrl; + +} diff --git a/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/order/vo/PayOrderSubmitRespVO.java b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/order/vo/PayOrderSubmitRespVO.java new file mode 100644 index 0000000..8dcd9df --- /dev/null +++ b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/order/vo/PayOrderSubmitRespVO.java @@ -0,0 +1,18 @@ +package cn.iocoder.yudao.module.pay.controller.admin.order.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +@Schema(description = "管理后台 - 支付订单提交 Response VO") +@Data +public class PayOrderSubmitRespVO { + + @Schema(description = "支付状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "10") // 参见 PayOrderStatusEnum 枚举 + private Integer status; + + @Schema(description = "展示模式", requiredMode = Schema.RequiredMode.REQUIRED, example = "url") // 参见 PayDisplayModeEnum 枚举 + private String displayMode; + @Schema(description = "展示内容", requiredMode = Schema.RequiredMode.REQUIRED) + private String displayContent; + +} diff --git a/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/refund/PayRefundController.java b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/refund/PayRefundController.java new file mode 100755 index 0000000..76fb045 --- /dev/null +++ b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/refund/PayRefundController.java @@ -0,0 +1,90 @@ +package cn.iocoder.yudao.module.pay.controller.admin.refund; + +import cn.hutool.core.collection.CollectionUtil; +import cn.iocoder.yudao.framework.apilog.core.annotation.ApiAccessLog; +import cn.iocoder.yudao.framework.common.pojo.CommonResult; +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils; +import cn.iocoder.yudao.module.pay.controller.admin.refund.vo.*; +import cn.iocoder.yudao.module.pay.convert.refund.PayRefundConvert; +import cn.iocoder.yudao.module.pay.dal.dataobject.app.PayAppDO; +import cn.iocoder.yudao.module.pay.dal.dataobject.refund.PayRefundDO; +import cn.iocoder.yudao.module.pay.service.app.PayAppService; +import cn.iocoder.yudao.module.pay.service.refund.PayRefundService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.annotation.Resource; +import jakarta.servlet.http.HttpServletResponse; +import jakarta.validation.Valid; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import java.io.IOException; +import java.util.List; +import java.util.Map; + +import static cn.iocoder.yudao.framework.apilog.core.enums.OperateTypeEnum.EXPORT; +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList; + +@Tag(name = "管理后台 - 退款订单") +@RestController +@RequestMapping("/pay/refund") +@Validated +public class PayRefundController { + + @Resource + private PayRefundService refundService; + @Resource + private PayAppService appService; + + @GetMapping("/get") + @Operation(summary = "获得退款订单") + @Parameter(name = "id", description = "编号", required = true, example = "1024") + @PreAuthorize("@ss.hasPermission('pay:refund:query')") + public CommonResult getRefund(@RequestParam("id") Long id) { + PayRefundDO refund = refundService.getRefund(id); + if (refund == null) { + return success(new PayRefundDetailsRespVO()); + } + + // 拼接数据 + PayAppDO app = appService.getApp(refund.getAppId()); + return success(PayRefundConvert.INSTANCE.convert(refund, app)); + } + + @GetMapping("/page") + @Operation(summary = "获得退款订单分页") + @PreAuthorize("@ss.hasPermission('pay:refund:query')") + public CommonResult> getRefundPage(@Valid PayRefundPageReqVO pageVO) { + PageResult pageResult = refundService.getRefundPage(pageVO); + if (CollectionUtil.isEmpty(pageResult.getList())) { + return success(new PageResult<>(pageResult.getTotal())); + } + + // 处理应用ID数据 + Map appMap = appService.getAppMap(convertList(pageResult.getList(), PayRefundDO::getAppId)); + return success(PayRefundConvert.INSTANCE.convertPage(pageResult, appMap)); + } + + @GetMapping("/export-excel") + @Operation(summary = "导出退款订单 Excel") + @PreAuthorize("@ss.hasPermission('pay:refund:export')") + @ApiAccessLog(operateType = EXPORT) + public void exportRefundExcel(@Valid PayRefundExportReqVO exportReqVO, + HttpServletResponse response) throws IOException { + List list = refundService.getRefundList(exportReqVO); + + // 拼接返回 + Map appMap = appService.getAppMap(convertList(list, PayRefundDO::getAppId)); + List excelList = PayRefundConvert.INSTANCE.convertList(list, appMap); + // 导出 Excel + ExcelUtils.write(response, "退款订单.xls", "数据", PayRefundExcelVO.class, excelList); + } + +} diff --git a/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/refund/vo/PayRefundBaseVO.java b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/refund/vo/PayRefundBaseVO.java new file mode 100755 index 0000000..25cadf4 --- /dev/null +++ b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/refund/vo/PayRefundBaseVO.java @@ -0,0 +1,78 @@ +package cn.iocoder.yudao.module.pay.controller.admin.refund.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.time.LocalDateTime; + +/** +* 退款订单 Base VO,提供给添加、修改、详细的子 VO 使用 +* 如果子 VO 存在差异的字段,请不要添加到这里,影响 Swagger 文档生成 +*/ +@Data +public class PayRefundBaseVO { + + @Schema(description = "外部退款号", requiredMode = Schema.RequiredMode.REQUIRED, example = "110") + private String no; + + @Schema(description = "应用编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") + private Long appId; + + @Schema(description = "渠道编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2048") + private Long channelId; + + @Schema(description = "渠道编码", requiredMode = Schema.RequiredMode.REQUIRED, example = "wx_app") + private String channelCode; + + @Schema(description = "订单编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") + private Long orderId; + + // ========== 商户相关字段 ========== + + @Schema(description = "商户订单编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "225") + private String merchantOrderId; + + @Schema(description = "商户退款订单号", requiredMode = Schema.RequiredMode.REQUIRED, example = "512") + private String merchantRefundId; + + @Schema(description = "异步通知地址", requiredMode = Schema.RequiredMode.REQUIRED) + private String notifyUrl; + + // ========== 退款相关字段 ========== + + @Schema(description = "退款状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "0") + private Integer status; + + @Schema(description = "支付金额", requiredMode = Schema.RequiredMode.REQUIRED, example = "100") + private Long payPrice; + + @Schema(description = "退款金额,单位分", requiredMode = Schema.RequiredMode.REQUIRED, example = "200") + private Long refundPrice; + + @Schema(description = "退款原因", requiredMode = Schema.RequiredMode.REQUIRED, example = "我要退了") + private String reason; + + @Schema(description = "用户 IP", requiredMode = Schema.RequiredMode.REQUIRED, example = "127.0.0.1") + private String userIp; + + // ========== 渠道相关字段 ========== + + @Schema(description = "渠道订单号", requiredMode = Schema.RequiredMode.REQUIRED, example = "233") + private String channelOrderNo; + + @Schema(description = "渠道退款单号", example = "2022") + private String channelRefundNo; + + @Schema(description = "退款成功时间") + private LocalDateTime successTime; + + @Schema(description = "调用渠道的错误码") + private String channelErrorCode; + + @Schema(description = "调用渠道的错误提示") + private String channelErrorMsg; + + @Schema(description = "支付渠道的额外参数") + private String channelNotifyData; + +} diff --git a/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/refund/vo/PayRefundDetailsRespVO.java b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/refund/vo/PayRefundDetailsRespVO.java new file mode 100755 index 0000000..8f50a3a --- /dev/null +++ b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/refund/vo/PayRefundDetailsRespVO.java @@ -0,0 +1,40 @@ +package cn.iocoder.yudao.module.pay.controller.admin.refund.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; + +import java.time.LocalDateTime; + +@Schema(description = "管理后台 - 退款订单详情 Response VO") +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class PayRefundDetailsRespVO extends PayRefundBaseVO { + + @Schema(description = "支付退款编号", requiredMode = Schema.RequiredMode.REQUIRED) + private Long id; + + @Schema(description = "应用名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "我是芋艿") + private String appName; + + @Schema(description = "支付订单", requiredMode = Schema.RequiredMode.REQUIRED) + private Order order; + + @Schema(description = "创建时间") + private LocalDateTime createTime; + + @Schema(description = "更新时间") + private LocalDateTime updateTime; + + @Schema(description = "管理后台 - 支付订单") + @Data + public static class Order { + + @Schema(description = "商品标题", requiredMode = Schema.RequiredMode.REQUIRED, example = "土豆") + private String subject; + + } + +} diff --git a/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/refund/vo/PayRefundExcelVO.java b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/refund/vo/PayRefundExcelVO.java new file mode 100755 index 0000000..758b6b6 --- /dev/null +++ b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/refund/vo/PayRefundExcelVO.java @@ -0,0 +1,61 @@ +package cn.iocoder.yudao.module.pay.controller.admin.refund.vo; + +import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat; +import cn.iocoder.yudao.framework.excel.core.convert.DictConvert; +import cn.iocoder.yudao.framework.excel.core.convert.MoneyConvert; +import cn.iocoder.yudao.module.pay.enums.DictTypeConstants; +import com.alibaba.excel.annotation.ExcelProperty; +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * 退款订单 Excel VO + * + * @author aquan + */ +@Data +public class PayRefundExcelVO { + + @ExcelProperty("支付退款编号") + private Long id; + + @ExcelProperty("创建时间") + private LocalDateTime createTime; + + @ExcelProperty(value = "支付金额", converter = MoneyConvert.class) + private Integer payPrice; + + @ExcelProperty(value = "退款金额", converter = MoneyConvert.class) + private Integer refundPrice; + + @ExcelProperty("商户退款单号") + private String merchantRefundId; + @ExcelProperty("退款单号") + private String no; + @ExcelProperty("渠道退款单号") + private String channelRefundNo; + + @ExcelProperty("商户支付单号") + private String merchantOrderId; + @ExcelProperty("渠道支付单号") + private String channelOrderNo; + + @ExcelProperty(value = "退款状态", converter = DictConvert.class) + @DictFormat(DictTypeConstants.REFUND_STATUS) + private Integer status; + + @ExcelProperty(value = "退款渠道", converter = DictConvert.class) + @DictFormat(DictTypeConstants.CHANNEL_CODE) + private String channelCode; + + @ExcelProperty("成功时间") + private LocalDateTime successTime; + + @ExcelProperty(value = "支付应用") + private String appName; + + @ExcelProperty("退款原因") + private String reason; + +} diff --git a/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/refund/vo/PayRefundExportReqVO.java b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/refund/vo/PayRefundExportReqVO.java new file mode 100755 index 0000000..645816e --- /dev/null +++ b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/refund/vo/PayRefundExportReqVO.java @@ -0,0 +1,40 @@ +package cn.iocoder.yudao.module.pay.controller.admin.refund.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import org.springframework.format.annotation.DateTimeFormat; + +import java.time.LocalDateTime; + +import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND; + +@Schema(description = "管理后台 - 退款订单 Excel 导出 Request VO,参数和 PayRefundPageReqVO 是一致的") +@Data +public class PayRefundExportReqVO { + + @Schema(description = "应用编号", example = "1024") + private Long appId; + + @Schema(description = "渠道编码", example = "wx_app") + private String channelCode; + + @Schema(description = "商户支付单号", example = "10") + private String merchantOrderId; + + @Schema(description = "商户退款单号", example = "20") + private String merchantRefundId; + + @Schema(description = "渠道支付单号", example = "30") + private String channelOrderNo; + + @Schema(description = "渠道退款单号", example = "40") + private String channelRefundNo; + + @Schema(description = "退款状态", example = "0") + private Integer status; + + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) + @Schema(description = "创建时间") + private LocalDateTime[] createTime; + +} diff --git a/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/refund/vo/PayRefundPageItemRespVO.java b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/refund/vo/PayRefundPageItemRespVO.java new file mode 100755 index 0000000..27c1285 --- /dev/null +++ b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/refund/vo/PayRefundPageItemRespVO.java @@ -0,0 +1,25 @@ +package cn.iocoder.yudao.module.pay.controller.admin.refund.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; + +import java.time.LocalDateTime; + +@Schema(description = "管理后台 - 退款订单分页查询 Response VO") +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class PayRefundPageItemRespVO extends PayRefundBaseVO { + + @Schema(description = "支付订单编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") + private Long id; + + @Schema(description = "应用名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "我是芋艿") + private String appName; + + @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED) + private LocalDateTime createTime; + +} diff --git a/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/refund/vo/PayRefundPageReqVO.java b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/refund/vo/PayRefundPageReqVO.java new file mode 100755 index 0000000..7ff1530 --- /dev/null +++ b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/refund/vo/PayRefundPageReqVO.java @@ -0,0 +1,45 @@ +package cn.iocoder.yudao.module.pay.controller.admin.refund.vo; + +import cn.iocoder.yudao.framework.common.pojo.PageParam; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; +import org.springframework.format.annotation.DateTimeFormat; + +import java.time.LocalDateTime; + +import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND; + +@Schema(description = "管理后台 - 退款订单分页 Request VO") +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class PayRefundPageReqVO extends PageParam { + + @Schema(description = "应用编号", example = "1024") + private Long appId; + + @Schema(description = "渠道编码", example = "wx_app") + private String channelCode; + + @Schema(description = "商户支付单号", example = "10") + private String merchantOrderId; + + @Schema(description = "商户退款单号", example = "20") + private String merchantRefundId; + + @Schema(description = "渠道支付单号", example = "30") + private String channelOrderNo; + + @Schema(description = "渠道退款单号", example = "40") + private String channelRefundNo; + + @Schema(description = "退款状态", example = "0") + private Integer status; + + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) + @Schema(description = "创建时间") + private LocalDateTime[] createTime; + +} diff --git a/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/transfer/PayTransferController.java b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/transfer/PayTransferController.java new file mode 100644 index 0000000..cfc30b2 --- /dev/null +++ b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/transfer/PayTransferController.java @@ -0,0 +1,91 @@ +package cn.iocoder.yudao.module.pay.controller.admin.transfer; + +import cn.iocoder.yudao.framework.apilog.core.annotation.ApiAccessLog; +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.module.pay.controller.admin.transfer.vo.PayTransferPageReqVO; +import cn.iocoder.yudao.module.pay.controller.admin.transfer.vo.PayTransferRespVO; +import cn.iocoder.yudao.module.pay.dal.dataobject.app.PayAppDO; +import cn.iocoder.yudao.module.pay.dal.dataobject.transfer.PayTransferDO; +import cn.iocoder.yudao.module.pay.service.app.PayAppService; +import cn.iocoder.yudao.module.pay.service.transfer.PayTransferService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.annotation.Resource; +import jakarta.servlet.http.HttpServletResponse; +import jakarta.validation.Valid; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import java.io.IOException; +import java.util.Map; + +import static cn.iocoder.yudao.framework.apilog.core.enums.OperateTypeEnum.EXPORT; +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; +import static cn.iocoder.yudao.framework.common.pojo.PageParam.PAGE_SIZE_NONE; +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList; + +@Tag(name = "管理后台 - 转账单") +@RestController +@RequestMapping("/pay/transfer") +@Validated +public class PayTransferController { + + @Resource + private PayTransferService payTransferService; + @Resource + private PayAppService payAppService; + + @GetMapping("/get") + @Operation(summary = "获得转账订单") + @PreAuthorize("@ss.hasPermission('pay:transfer:query')") + public CommonResult getTransfer(@RequestParam("id") Long id) { + PayTransferDO transfer = payTransferService.getTransfer(id); + if (transfer == null) { + return success(new PayTransferRespVO()); + } + + // 拼接数据 + PayAppDO app = payAppService.getApp(transfer.getAppId()); + return success(BeanUtils.toBean(transfer, PayTransferRespVO.class, transferVO -> { + if (app != null) { + transferVO.setAppName(app.getName()); + } + })); + } + + @GetMapping("/page") + @Operation(summary = "获得转账订单分页") + @PreAuthorize("@ss.hasPermission('pay:transfer:query')") + public CommonResult> getTransferPage(@Valid PayTransferPageReqVO pageVO) { + PageResult pageResult = payTransferService.getTransferPage(pageVO); + + // 拼接数据 + Map apps = payAppService.getAppMap(convertList(pageResult.getList(), PayTransferDO::getAppId)); + return success(BeanUtils.toBean(pageResult, PayTransferRespVO.class, transferVO -> { + if (apps.containsKey(transferVO.getAppId())) { + transferVO.setAppName(apps.get(transferVO.getAppId()).getName()); + } + })); + } + + @GetMapping("/export-excel") + @Operation(summary = "导出转账订单 Excel") + @PreAuthorize("@ss.hasPermission('pay:transfer:export')") + @ApiAccessLog(operateType = EXPORT) + public void exportTransfer(PayTransferPageReqVO pageReqVO, + HttpServletResponse response) throws IOException { + pageReqVO.setPageSize(PAGE_SIZE_NONE); + PageResult pageResult = getTransferPage(pageReqVO).getData(); + + // 导出 Excel + ExcelUtils.write(response, "转账订单.xls", "数据", PayTransferRespVO.class, pageResult.getList()); + } + +} diff --git a/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/transfer/vo/PayTransferCreateRespVO.java b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/transfer/vo/PayTransferCreateRespVO.java new file mode 100644 index 0000000..9cb44bf --- /dev/null +++ b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/transfer/vo/PayTransferCreateRespVO.java @@ -0,0 +1,16 @@ +package cn.iocoder.yudao.module.pay.controller.admin.transfer.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +@Schema(description = "管理后台 - 发起转账 Response VO") +@Data +public class PayTransferCreateRespVO { + + @Schema(description = "转账单编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + private Long id; + + @Schema(description = "转账状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") // 参见 PayTransferStatusEnum 枚举 + private Integer status; + +} diff --git a/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/transfer/vo/PayTransferPageReqVO.java b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/transfer/vo/PayTransferPageReqVO.java new file mode 100644 index 0000000..08a6d45 --- /dev/null +++ b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/transfer/vo/PayTransferPageReqVO.java @@ -0,0 +1,45 @@ +package cn.iocoder.yudao.module.pay.controller.admin.transfer.vo; + +import lombok.*; +import io.swagger.v3.oas.annotations.media.Schema; +import cn.iocoder.yudao.framework.common.pojo.PageParam; +import org.springframework.format.annotation.DateTimeFormat; +import java.time.LocalDateTime; + +import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND; + +@Schema(description = "管理后台 - 转账单分页 Request VO") +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class PayTransferPageReqVO extends PageParam { + + @Schema(description = "转账单号") + private String no; + + @Schema(description = "应用编号", example = "12831") + private Long appId; + + @Schema(description = "渠道编码", example = "wx_app") + private String channelCode; + + @Schema(description = "商户转账单编号", example = "17481") + private String merchantOrderId; + + @Schema(description = "转账状态", example = "2") + private Integer status; + + @Schema(description = "收款人姓名", example = "王五") + private String userName; + + @Schema(description = "收款人账号", example = "26589") + private String userAccount; + + @Schema(description = "渠道转账单号") + private String channelTransferNo; + + @Schema(description = "创建时间") + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) + private LocalDateTime[] createTime; + +} diff --git a/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/transfer/vo/PayTransferRespVO.java b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/transfer/vo/PayTransferRespVO.java new file mode 100644 index 0000000..9e98db1 --- /dev/null +++ b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/transfer/vo/PayTransferRespVO.java @@ -0,0 +1,100 @@ +package cn.iocoder.yudao.module.pay.controller.admin.transfer.vo; + +import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat; +import cn.iocoder.yudao.framework.excel.core.convert.DictConvert; +import cn.iocoder.yudao.framework.excel.core.convert.MoneyConvert; +import cn.iocoder.yudao.module.pay.enums.DictTypeConstants; + +import com.alibaba.excel.annotation.ExcelIgnoreUnannotated; +import com.alibaba.excel.annotation.ExcelProperty; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.*; +import java.time.LocalDateTime; +import java.util.Map; + +@Schema(description = "管理后台 - 转账单 Response VO") +@Data +@ExcelIgnoreUnannotated +public class PayTransferRespVO { + + @ExcelProperty("转账单编号") + @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2931") + private Long id; + + @ExcelProperty("转账单号") + @Schema(description = "转账单号", requiredMode = Schema.RequiredMode.REQUIRED) + private String no; + + @Schema(description = "应用编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "12831") + private Long appId; + + @ExcelProperty("应用名称") + @Schema(description = "应用名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋艿") + private String appName; + + @Schema(description = "转账渠道编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "24833") + private Long channelId; + + @ExcelProperty(value = "转账渠道", converter = DictConvert.class) + @DictFormat(DictTypeConstants.CHANNEL_CODE) + @Schema(description = "转账渠道编码", requiredMode = Schema.RequiredMode.REQUIRED) + private String channelCode; + + @ExcelProperty("商户转账单编号") + @Schema(description = "商户转账单编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "17481") + private String merchantTransferId; + + @ExcelProperty(value = "转账状态", converter = DictConvert.class) + @DictFormat(DictTypeConstants.TRANSFER_STATUS) + @Schema(description = "转账状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "2") + private Integer status; + + @ExcelProperty("转账成功时间") + @Schema(description = "转账成功时间") + private LocalDateTime successTime; + + @Schema(description = "转账金额", requiredMode = Schema.RequiredMode.REQUIRED, example = "964") + @ExcelProperty(value = "转账金额", converter = MoneyConvert.class) + private Integer price; + + @ExcelProperty("转账标题") + @Schema(description = "转账标题", requiredMode = Schema.RequiredMode.REQUIRED, example = "冲冲冲!") + private String subject; + + @Schema(description = "收款人姓名", example = "王五") + @ExcelProperty("收款人姓名") + private String userName; + + @Schema(description = "收款人账号", requiredMode = Schema.RequiredMode.REQUIRED, example = "26589") + @ExcelProperty("收款人账号") + private String userAccount; + + @Schema(description = "异步通知商户地址", requiredMode = Schema.RequiredMode.REQUIRED, example = "https://www.iocoder.cn") + private String notifyUrl; + + @ExcelProperty("用户 IP") + @Schema(description = "用户 IP", requiredMode = Schema.RequiredMode.REQUIRED) + private String userIp; + + @Schema(description = "渠道的额外参数") + private Map channelExtras; + + @Schema(description = "渠道转账单号") + @ExcelProperty("渠道转账单号") + private String channelTransferNo; + + @Schema(description = "调用渠道的错误码") + private String channelErrorCode; + + @ExcelProperty("渠道错误提示") + @Schema(description = "调用渠道的错误提示") + private String channelErrorMsg; + + @Schema(description = "渠道的同步/异步通知的内容") + private String channelNotifyData; + + @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED) + @ExcelProperty("创建时间") + private LocalDateTime createTime; + +} diff --git a/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/wallet/PayWalletController.java b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/wallet/PayWalletController.java new file mode 100644 index 0000000..54fa004 --- /dev/null +++ b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/wallet/PayWalletController.java @@ -0,0 +1,70 @@ +package cn.iocoder.yudao.module.pay.controller.admin.wallet; + +import cn.iocoder.yudao.framework.common.pojo.CommonResult; +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.module.pay.controller.admin.wallet.vo.wallet.PayWalletPageReqVO; +import cn.iocoder.yudao.module.pay.controller.admin.wallet.vo.wallet.PayWalletRespVO; +import cn.iocoder.yudao.module.pay.controller.admin.wallet.vo.wallet.PayWalletUpdateBalanceReqVO; +import cn.iocoder.yudao.module.pay.controller.admin.wallet.vo.wallet.PayWalletUserReqVO; +import cn.iocoder.yudao.module.pay.convert.wallet.PayWalletConvert; +import cn.iocoder.yudao.module.pay.dal.dataobject.wallet.PayWalletDO; +import cn.iocoder.yudao.module.pay.enums.wallet.PayWalletBizTypeEnum; +import cn.iocoder.yudao.module.pay.service.wallet.PayWalletService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.annotation.Resource; +import jakarta.validation.Valid; +import lombok.extern.slf4j.Slf4j; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import static cn.iocoder.yudao.framework.common.enums.UserTypeEnum.MEMBER; +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; +import static cn.iocoder.yudao.module.pay.enums.ErrorCodeConstants.WALLET_NOT_FOUND; + +@Tag(name = "管理后台 - 用户钱包") +@RestController +@RequestMapping("/pay/wallet") +@Validated +@Slf4j +public class PayWalletController { + + @Resource + private PayWalletService payWalletService; + + @GetMapping("/get") + @PreAuthorize("@ss.hasPermission('pay:wallet:query')") + @Operation(summary = "获得用户钱包明细") + public CommonResult getWallet(PayWalletUserReqVO reqVO) { + PayWalletDO wallet = payWalletService.getOrCreateWallet(reqVO.getUserId(), MEMBER.getValue()); + return success(PayWalletConvert.INSTANCE.convert02(wallet)); + } + + @GetMapping("/page") + @Operation(summary = "获得会员钱包分页") + @PreAuthorize("@ss.hasPermission('pay:wallet:query')") + public CommonResult> getWalletPage(@Valid PayWalletPageReqVO pageVO) { + PageResult pageResult = payWalletService.getWalletPage(pageVO); + return success(PayWalletConvert.INSTANCE.convertPage(pageResult)); + } + + @PutMapping("/update-balance") + @Operation(summary = "更新会员用户余额") + @PreAuthorize("@ss.hasPermission('pay:wallet:update-balance')") + public CommonResult updateWalletBalance(@Valid @RequestBody PayWalletUpdateBalanceReqVO updateReqVO) { + // 获得用户钱包 + PayWalletDO wallet = payWalletService.getOrCreateWallet(updateReqVO.getUserId(), MEMBER.getValue()); + if (wallet == null) { + log.error("[updateWalletBalance],updateReqVO({}) 用户钱包不存在.", updateReqVO); + throw exception(WALLET_NOT_FOUND); + } + + // 更新钱包余额 + payWalletService.addWalletBalance(wallet.getId(), String.valueOf(updateReqVO.getUserId()), + PayWalletBizTypeEnum.UPDATE_BALANCE, updateReqVO.getBalance()); + return success(true); + } + +} diff --git a/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/wallet/PayWalletRechargeController.java b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/wallet/PayWalletRechargeController.java new file mode 100644 index 0000000..c5e84aa --- /dev/null +++ b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/wallet/PayWalletRechargeController.java @@ -0,0 +1,59 @@ +package cn.iocoder.yudao.module.pay.controller.admin.wallet; + +import cn.iocoder.yudao.framework.common.pojo.CommonResult; +import cn.iocoder.yudao.module.pay.api.notify.dto.PayOrderNotifyReqDTO; +import cn.iocoder.yudao.module.pay.api.notify.dto.PayRefundNotifyReqDTO; +import cn.iocoder.yudao.module.pay.service.wallet.PayWalletRechargeService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.extern.slf4j.Slf4j; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import jakarta.annotation.Resource; +import jakarta.annotation.security.PermitAll; +import jakarta.validation.Valid; + +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; +import static cn.iocoder.yudao.framework.common.util.servlet.ServletUtils.getClientIP; + +@Tag(name = "管理后台 - 钱包充值") +@RestController +@RequestMapping("/pay/wallet-recharge") +@Validated +@Slf4j +public class PayWalletRechargeController { + + @Resource + private PayWalletRechargeService walletRechargeService; + + @PostMapping("/update-paid") + @Operation(summary = "更新钱包充值为已充值") // 由 pay-module 支付服务,进行回调,可见 PayNotifyJob + @PermitAll // 无需登录, 内部校验实现 + public CommonResult updateWalletRechargerPaid(@Valid @RequestBody PayOrderNotifyReqDTO notifyReqDTO) { + walletRechargeService.updateWalletRechargerPaid(Long.valueOf(notifyReqDTO.getMerchantOrderId()), + notifyReqDTO.getPayOrderId()); + return success(true); + } + + @PostMapping("/refund") + @Operation(summary = "发起钱包充值退款") + @Parameter(name = "id", description = "编号", required = true, example = "1024") + public CommonResult refundWalletRecharge(@RequestParam("id") Long id) { + walletRechargeService.refundWalletRecharge(id, getClientIP()); + return success(true); + } + + @PostMapping("/update-refunded") + @Operation(summary = "更新钱包充值为已退款") // 由 pay-module 支付服务,进行回调,可见 PayNotifyJob + @PermitAll // 无需登录, 内部校验实现 + public CommonResult updateWalletRechargeRefunded(@RequestBody PayRefundNotifyReqDTO notifyReqDTO) { + walletRechargeService.updateWalletRechargeRefunded( + Long.valueOf(notifyReqDTO.getMerchantOrderId()), + Long.valueOf(notifyReqDTO.getMerchantRefundId()), + notifyReqDTO.getPayRefundId()); + return success(true); + } + +} diff --git a/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/wallet/PayWalletRechargePackageController.java b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/wallet/PayWalletRechargePackageController.java new file mode 100644 index 0000000..eb8675b --- /dev/null +++ b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/wallet/PayWalletRechargePackageController.java @@ -0,0 +1,75 @@ +package cn.iocoder.yudao.module.pay.controller.admin.wallet; + +import cn.iocoder.yudao.framework.common.pojo.CommonResult; +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.module.pay.controller.admin.wallet.vo.rechargepackage.WalletRechargePackageCreateReqVO; +import cn.iocoder.yudao.module.pay.controller.admin.wallet.vo.rechargepackage.WalletRechargePackagePageReqVO; +import cn.iocoder.yudao.module.pay.controller.admin.wallet.vo.rechargepackage.WalletRechargePackageRespVO; +import cn.iocoder.yudao.module.pay.controller.admin.wallet.vo.rechargepackage.WalletRechargePackageUpdateReqVO; +import cn.iocoder.yudao.module.pay.convert.wallet.PayWalletRechargePackageConvert; +import cn.iocoder.yudao.module.pay.dal.dataobject.wallet.PayWalletRechargePackageDO; +import cn.iocoder.yudao.module.pay.service.wallet.PayWalletRechargePackageService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.tags.Tag; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import jakarta.annotation.Resource; +import jakarta.validation.Valid; + +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; + + +@Tag(name = "管理后台 - 钱包充值套餐") +@RestController +@RequestMapping("/pay/wallet-recharge-package") +@Validated +public class PayWalletRechargePackageController { + + @Resource + private PayWalletRechargePackageService walletRechargePackageService; + + @PostMapping("/create") + @Operation(summary = "创建钱包充值套餐") + @PreAuthorize("@ss.hasPermission('pay:wallet-recharge-package:create')") + public CommonResult createWalletRechargePackage(@Valid @RequestBody WalletRechargePackageCreateReqVO createReqVO) { + return success(walletRechargePackageService.createWalletRechargePackage(createReqVO)); + } + + @PutMapping("/update") + @Operation(summary = "更新钱包充值套餐") + @PreAuthorize("@ss.hasPermission('pay:wallet-recharge-package:update')") + public CommonResult updateWalletRechargePackage(@Valid @RequestBody WalletRechargePackageUpdateReqVO updateReqVO) { + walletRechargePackageService.updateWalletRechargePackage(updateReqVO); + return success(true); + } + + @DeleteMapping("/delete") + @Operation(summary = "删除钱包充值套餐") + @Parameter(name = "id", description = "编号", required = true) + @PreAuthorize("@ss.hasPermission('pay:wallet-recharge-package:delete')") + public CommonResult deleteWalletRechargePackage(@RequestParam("id") Long id) { + walletRechargePackageService.deleteWalletRechargePackage(id); + return success(true); + } + + @GetMapping("/get") + @Operation(summary = "获得钱包充值套餐") + @Parameter(name = "id", description = "编号", required = true, example = "1024") + @PreAuthorize("@ss.hasPermission('pay:wallet-recharge-package:query')") + public CommonResult getWalletRechargePackage(@RequestParam("id") Long id) { + PayWalletRechargePackageDO walletRechargePackage = walletRechargePackageService.getWalletRechargePackage(id); + return success(PayWalletRechargePackageConvert.INSTANCE.convert(walletRechargePackage)); + } + + @GetMapping("/page") + @Operation(summary = "获得钱包充值套餐分页") + @PreAuthorize("@ss.hasPermission('pay:wallet-recharge-package:query')") + public CommonResult> getWalletRechargePackagePage(@Valid WalletRechargePackagePageReqVO pageVO) { + PageResult pageResult = walletRechargePackageService.getWalletRechargePackagePage(pageVO); + return success(PayWalletRechargePackageConvert.INSTANCE.convertPage(pageResult)); + } + +} diff --git a/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/wallet/PayWalletTransactionController.java b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/wallet/PayWalletTransactionController.java new file mode 100644 index 0000000..4b9aee6 --- /dev/null +++ b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/wallet/PayWalletTransactionController.java @@ -0,0 +1,43 @@ +package cn.iocoder.yudao.module.pay.controller.admin.wallet; + +import cn.iocoder.yudao.framework.common.pojo.CommonResult; +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.module.pay.controller.admin.wallet.vo.transaction.PayWalletTransactionPageReqVO; +import cn.iocoder.yudao.module.pay.controller.admin.wallet.vo.transaction.PayWalletTransactionRespVO; +import cn.iocoder.yudao.module.pay.convert.wallet.PayWalletTransactionConvert; +import cn.iocoder.yudao.module.pay.dal.dataobject.wallet.PayWalletTransactionDO; +import cn.iocoder.yudao.module.pay.service.wallet.PayWalletTransactionService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.extern.slf4j.Slf4j; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import jakarta.annotation.Resource; +import jakarta.validation.Valid; + +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; + +@Tag(name = "管理后台 - 钱包余额明细") +@RestController +@RequestMapping("/pay/wallet-transaction") +@Validated +@Slf4j +public class PayWalletTransactionController { + + @Resource + private PayWalletTransactionService payWalletTransactionService; + + @GetMapping("/page") + @Operation(summary = "获得钱包流水分页") + @PreAuthorize("@ss.hasPermission('pay:wallet:query')") + public CommonResult> getWalletTransactionPage( + @Valid PayWalletTransactionPageReqVO pageReqVO) { + PageResult result = payWalletTransactionService.getWalletTransactionPage(pageReqVO); + return success(PayWalletTransactionConvert.INSTANCE.convertPage2(result)); + } + +} diff --git a/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/wallet/vo/rechargepackage/WalletRechargePackageBaseVO.java b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/wallet/vo/rechargepackage/WalletRechargePackageBaseVO.java new file mode 100644 index 0000000..ea6f0c4 --- /dev/null +++ b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/wallet/vo/rechargepackage/WalletRechargePackageBaseVO.java @@ -0,0 +1,31 @@ +package cn.iocoder.yudao.module.pay.controller.admin.wallet.vo.rechargepackage; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import jakarta.validation.constraints.NotNull; + +/** + * 充值套餐 Base VO,提供给添加、修改、详细的子 VO 使用 + * 如果子 VO 存在差异的字段,请不要添加到这里,影响 Swagger 文档生成 + */ +@Data +public class WalletRechargePackageBaseVO { + + @Schema(description = "套餐名", requiredMode = Schema.RequiredMode.REQUIRED, example = "李四") + @NotNull(message = "套餐名不能为空") + private String name; + + @Schema(description = "支付金额", requiredMode = Schema.RequiredMode.REQUIRED, example = "16454") + @NotNull(message = "支付金额不能为空") + private Integer payPrice; + + @Schema(description = "赠送金额", requiredMode = Schema.RequiredMode.REQUIRED, example = "20887") + @NotNull(message = "赠送金额不能为空") + private Integer bonusPrice; + + @Schema(description = "状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "2") + @NotNull(message = "状态不能为空") + private Byte status; + +} diff --git a/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/wallet/vo/rechargepackage/WalletRechargePackageCreateReqVO.java b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/wallet/vo/rechargepackage/WalletRechargePackageCreateReqVO.java new file mode 100644 index 0000000..4232a99 --- /dev/null +++ b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/wallet/vo/rechargepackage/WalletRechargePackageCreateReqVO.java @@ -0,0 +1,14 @@ +package cn.iocoder.yudao.module.pay.controller.admin.wallet.vo.rechargepackage; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; + +@Schema(description = "管理后台 - 充值套餐创建 Request VO") +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class WalletRechargePackageCreateReqVO extends WalletRechargePackageBaseVO { + +} diff --git a/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/wallet/vo/rechargepackage/WalletRechargePackagePageReqVO.java b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/wallet/vo/rechargepackage/WalletRechargePackagePageReqVO.java new file mode 100644 index 0000000..346e859 --- /dev/null +++ b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/wallet/vo/rechargepackage/WalletRechargePackagePageReqVO.java @@ -0,0 +1,30 @@ +package cn.iocoder.yudao.module.pay.controller.admin.wallet.vo.rechargepackage; + +import cn.iocoder.yudao.framework.common.pojo.PageParam; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; +import org.springframework.format.annotation.DateTimeFormat; + +import java.time.LocalDateTime; + +import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND; + +@Schema(description = "管理后台 - 充值套餐分页 Request VO") +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class WalletRechargePackagePageReqVO extends PageParam { + + @Schema(description = "套餐名", example = "李四") + private String name; + + @Schema(description = "状态", example = "2") + private Integer status; + + @Schema(description = "创建时间") + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) + private LocalDateTime[] createTime; + +} diff --git a/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/wallet/vo/rechargepackage/WalletRechargePackageRespVO.java b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/wallet/vo/rechargepackage/WalletRechargePackageRespVO.java new file mode 100644 index 0000000..84abaf7 --- /dev/null +++ b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/wallet/vo/rechargepackage/WalletRechargePackageRespVO.java @@ -0,0 +1,19 @@ +package cn.iocoder.yudao.module.pay.controller.admin.wallet.vo.rechargepackage; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.*; +import java.time.LocalDateTime; + +@Schema(description = "管理后台 - 充值套餐 Response VO") +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class WalletRechargePackageRespVO extends WalletRechargePackageBaseVO { + + @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "9032") + private Long id; + + @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED) + private LocalDateTime createTime; + +} diff --git a/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/wallet/vo/rechargepackage/WalletRechargePackageUpdateReqVO.java b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/wallet/vo/rechargepackage/WalletRechargePackageUpdateReqVO.java new file mode 100644 index 0000000..77b230f --- /dev/null +++ b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/wallet/vo/rechargepackage/WalletRechargePackageUpdateReqVO.java @@ -0,0 +1,20 @@ +package cn.iocoder.yudao.module.pay.controller.admin.wallet.vo.rechargepackage; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; + +import jakarta.validation.constraints.NotNull; + +@Schema(description = "管理后台 - 充值套餐更新 Request VO") +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class WalletRechargePackageUpdateReqVO extends WalletRechargePackageBaseVO { + + @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "9032") + @NotNull(message = "编号不能为空") + private Long id; + +} diff --git a/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/wallet/vo/transaction/PayWalletTransactionPageReqVO.java b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/wallet/vo/transaction/PayWalletTransactionPageReqVO.java new file mode 100644 index 0000000..7491b9e --- /dev/null +++ b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/wallet/vo/transaction/PayWalletTransactionPageReqVO.java @@ -0,0 +1,23 @@ +package cn.iocoder.yudao.module.pay.controller.admin.wallet.vo.transaction; + +import cn.iocoder.yudao.framework.common.enums.UserTypeEnum; +import cn.iocoder.yudao.framework.common.pojo.PageParam; +import cn.iocoder.yudao.framework.common.validation.InEnum; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +@Schema(description = "管理后台 - 钱包流水分页 Request VO") +@Data +public class PayWalletTransactionPageReqVO extends PageParam { + + @Schema(description = "钱包编号", example = "888") + private Long walletId; + + @Schema(description = "用户编号", example = "1024") + private Long userId; + + @Schema(description = "用户类型", example = "1") + @InEnum(UserTypeEnum.class) + private Integer userType; + +} diff --git a/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/wallet/vo/transaction/PayWalletTransactionRespVO.java b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/wallet/vo/transaction/PayWalletTransactionRespVO.java new file mode 100644 index 0000000..6203b78 --- /dev/null +++ b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/wallet/vo/transaction/PayWalletTransactionRespVO.java @@ -0,0 +1,35 @@ +package cn.iocoder.yudao.module.pay.controller.admin.wallet.vo.transaction; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.time.LocalDateTime; + +@Schema(description = "用户 APP - 钱包流水分页 Response VO") +@Data +public class PayWalletTransactionRespVO { + + @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + private Long id; + + @Schema(description = "钱包编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "5") + private Long walletId; + + @Schema(description = "业务分类", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + private Integer bizType; + + @Schema(description = "交易金额,单位分", requiredMode = Schema.RequiredMode.REQUIRED, example = "100") + private Long price; + + @Schema(description = "流水标题", requiredMode = Schema.RequiredMode.REQUIRED, example = "土豆土豆") + private String title; + + @Schema(description = "交易后的余额,单位分", requiredMode = Schema.RequiredMode.REQUIRED, example = "100") + private Long balance; + + @Schema(description = "交易时间", requiredMode = Schema.RequiredMode.REQUIRED) + private LocalDateTime createTime; + + // TODO @jason:merchantOrderId 字段,需要在 PayWalletTransaction 存储下;然后,前端也返回下这个字段,界面也展示下商户名 + +} diff --git a/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/wallet/vo/wallet/PayWalletBaseVO.java b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/wallet/vo/wallet/PayWalletBaseVO.java new file mode 100644 index 0000000..2e319de --- /dev/null +++ b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/wallet/vo/wallet/PayWalletBaseVO.java @@ -0,0 +1,39 @@ +package cn.iocoder.yudao.module.pay.controller.admin.wallet.vo.wallet; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import jakarta.validation.constraints.NotNull; + +/** + * 用户钱包 Base VO,提供给添加、修改、详细的子 VO 使用 + * 如果子 VO 存在差异的字段,请不要添加到这里,影响 Swagger 文档生成 + */ +@Data +public class PayWalletBaseVO { + + @Schema(description = "用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "20020") + @NotNull(message = "用户编号不能为空") + private Long userId; + + @Schema(description = "用户类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + @NotNull(message = "用户类型不能为空") + private Integer userType; + + @Schema(description = "余额,单位分", requiredMode = Schema.RequiredMode.REQUIRED) + @NotNull(message = "余额,单位分不能为空") + private Integer balance; + + @Schema(description = "累计支出,单位分", requiredMode = Schema.RequiredMode.REQUIRED) + @NotNull(message = "累计支出,单位分不能为空") + private Integer totalExpense; + + @Schema(description = "累计充值,单位分", requiredMode = Schema.RequiredMode.REQUIRED) + @NotNull(message = "累计充值,单位分不能为空") + private Integer totalRecharge; + + @Schema(description = "冻结金额,单位分", requiredMode = Schema.RequiredMode.REQUIRED, example = "20737") + @NotNull(message = "冻结金额,单位分不能为空") + private Integer freezePrice; + +} \ No newline at end of file diff --git a/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/wallet/vo/wallet/PayWalletPageReqVO.java b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/wallet/vo/wallet/PayWalletPageReqVO.java new file mode 100644 index 0000000..a316982 --- /dev/null +++ b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/wallet/vo/wallet/PayWalletPageReqVO.java @@ -0,0 +1,33 @@ +package cn.iocoder.yudao.module.pay.controller.admin.wallet.vo.wallet; + +import cn.iocoder.yudao.framework.common.enums.UserTypeEnum; +import cn.iocoder.yudao.framework.common.pojo.PageParam; +import cn.iocoder.yudao.framework.common.validation.InEnum; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; +import org.springframework.format.annotation.DateTimeFormat; + +import java.time.LocalDateTime; + +import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND; + +@Schema(description = "管理后台 - 会员钱包分页 Request VO") +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class PayWalletPageReqVO extends PageParam { + + @Schema(description = "用户编号", example = "1024") + private Long userId; + + @Schema(description = "用户类型", example = "1") + @InEnum(value = UserTypeEnum.class) + private Integer userType; + + @Schema(description = "创建时间") + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) + private LocalDateTime[] createTime; + +} diff --git a/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/wallet/vo/wallet/PayWalletRespVO.java b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/wallet/vo/wallet/PayWalletRespVO.java new file mode 100644 index 0000000..01b35fb --- /dev/null +++ b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/wallet/vo/wallet/PayWalletRespVO.java @@ -0,0 +1,22 @@ +package cn.iocoder.yudao.module.pay.controller.admin.wallet.vo.wallet; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; + +import java.time.LocalDateTime; + +@Schema(description = "管理后台 - 用户钱包 Response VO") +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class PayWalletRespVO extends PayWalletBaseVO { + + @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "29528") + private Long id; + + @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED) + private LocalDateTime createTime; + +} diff --git a/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/wallet/vo/wallet/PayWalletUpdateBalanceReqVO.java b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/wallet/vo/wallet/PayWalletUpdateBalanceReqVO.java new file mode 100644 index 0000000..7569bca --- /dev/null +++ b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/wallet/vo/wallet/PayWalletUpdateBalanceReqVO.java @@ -0,0 +1,19 @@ +package cn.iocoder.yudao.module.pay.controller.admin.wallet.vo.wallet; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +@Schema(description = "管理后台 - 修改钱包余额 Request VO") +@Data +public class PayWalletUpdateBalanceReqVO { + + @Schema(description = "用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "23788") + @NotNull(message = "用户编号不能为空") + private Long userId; + + @Schema(description = "变动余额,正数为增加,负数为减少", requiredMode = Schema.RequiredMode.REQUIRED, example = "100") + @NotNull(message = "变动余额不能为空") + private Integer balance; + +} diff --git a/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/wallet/vo/wallet/PayWalletUserReqVO.java b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/wallet/vo/wallet/PayWalletUserReqVO.java new file mode 100644 index 0000000..e6d4986 --- /dev/null +++ b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/wallet/vo/wallet/PayWalletUserReqVO.java @@ -0,0 +1,16 @@ +package cn.iocoder.yudao.module.pay.controller.admin.wallet.vo.wallet; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import jakarta.validation.constraints.NotNull; + +@Schema(description = "管理后台 - 用户钱包明细 Request VO") +@Data +public class PayWalletUserReqVO { + + @Schema(description = "用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + @NotNull(message = "用户编号不能为空") + private Long userId; + +} diff --git a/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/app/channel/AppPayChannelController.java b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/app/channel/AppPayChannelController.java new file mode 100644 index 0000000..9e9f798 --- /dev/null +++ b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/app/channel/AppPayChannelController.java @@ -0,0 +1,39 @@ +package cn.iocoder.yudao.module.pay.controller.app.channel; + +import cn.iocoder.yudao.framework.common.pojo.CommonResult; +import cn.iocoder.yudao.module.pay.dal.dataobject.channel.PayChannelDO; +import cn.iocoder.yudao.module.pay.service.channel.PayChannelService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.annotation.Resource; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import java.util.List; +import java.util.Set; + +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet; + +@Tag(name = "用户 App - 支付渠道") +@RestController +@RequestMapping("/pay/channel") +@Validated +public class AppPayChannelController { + + @Resource + private PayChannelService channelService; + + @GetMapping("/get-enable-code-list") + @Operation(summary = "获得指定应用的开启的支付渠道编码列表") + @Parameter(name = "appId", description = "应用编号", required = true, example = "1") + public CommonResult> getEnableChannelCodeList(@RequestParam("appId") Long appId) { + List channels = channelService.getEnableChannelList(appId); + return success(convertSet(channels, PayChannelDO::getCode)); + } + +} diff --git a/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/app/order/AppPayOrderController.http b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/app/order/AppPayOrderController.http new file mode 100644 index 0000000..4fe1898 --- /dev/null +++ b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/app/order/AppPayOrderController.http @@ -0,0 +1,63 @@ +### /pay/create 提交支付订单【alipay_pc】 +POST {{appApi}}/pay/order/submit +Content-Type: application/json +Authorization: Bearer {{appToken}} +tenant-id: {{appTenantId}} + +{ + "id": 174, + "channelCode": "alipay_pc" +} + +### /pay/create 提交支付订单【wx_bar】 +POST {{appApi}}/pay/order/submit +Content-Type: application/json +Authorization: Bearer {{appToken}} +tenant-id: {{appTenantId}} + +{ + "id": 202, + "channelCode": "wx_bar", + "channelExtras": { + "authCode": "134042110834344848" + } +} + +### /pay/create 提交支付订单【wx_pub】 +POST {{appApi}}/pay/order/submit +Content-Type: application/json +Authorization: Bearer {{appToken}} +tenant-id: {{appTenantId}} + +{ + "id": 202, + "channelCode": "wx_pub", + "channelExtras": { + "openid": "ockUAwIZ-0OeMZl9ogcZ4ILrGba0" + } +} + +### /pay/create 提交支付订单【wx_lite】 +POST {{appApi}}/pay/order/submit +Content-Type: application/json +Authorization: Bearer {{appToken}} +tenant-id: {{appTenantId}} + +{ + "id": 202, + "channelCode": "wx_lite", + "channelExtras": { + "openid": "oLefc4g5GjKWHJjLjMSXB3wX0fD0" + } +} + +### /pay/create 提交支付订单【wx_native】 +POST {{appApi}}/pay/order/submit +Content-Type: application/json +Authorization: Bearer {{appToken}} +tenant-id: {{appTenantId}} + +{ + "id": 202, + "channelCode": "wx_native" +} diff --git a/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/app/order/AppPayOrderController.java b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/app/order/AppPayOrderController.java new file mode 100644 index 0000000..88eeb09 --- /dev/null +++ b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/app/order/AppPayOrderController.java @@ -0,0 +1,81 @@ +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.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; +import cn.iocoder.yudao.module.pay.controller.app.order.vo.AppPayOrderSubmitRespVO; +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.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; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.Parameters; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import java.util.Objects; + +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; +import static cn.iocoder.yudao.framework.common.util.servlet.ServletUtils.getClientIP; +import static cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils.getLoginUserId; +import static cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils.getLoginUserType; + +@Tag(name = "用户 APP - 支付订单") +@RestController +@RequestMapping("/pay/order") +@Validated +@Slf4j +public class AppPayOrderController { + + @Resource + private PayOrderService payOrderService; + @Resource + private PayWalletService payWalletService; + + @GetMapping("/get") + @Operation(summary = "获得支付订单") + @Parameters({ + @Parameter(name = "id", description = "编号", required = true, example = "1024"), + @Parameter(name = "sync", description = "是否同步", example = "true") + }) + public CommonResult getOrder(@RequestParam("id") Long id, + @RequestParam(value = "sync", required = false) Boolean sync) { + PayOrderDO order = payOrderService.getOrder(id); + // sync 仅在等待支付 + if (Boolean.TRUE.equals(sync) && PayOrderStatusEnum.isWaiting(order.getStatus())) { + payOrderService.syncOrderQuietly(order.getId()); + // 重新查询,因为同步后,可能会有变化 + order = payOrderService.getOrder(id); + } + return success(BeanUtils.toBean(order, PayOrderRespVO.class)); + } + + @PostMapping("/submit") + @Operation(summary = "提交支付订单") + public CommonResult submitPayOrder(@RequestBody AppPayOrderSubmitReqVO reqVO) { + // 1. 钱包支付事,需要额外传 user_id 和 user_type + if (Objects.equals(reqVO.getChannelCode(), PayChannelEnum.WALLET.getCode())) { + if (reqVO.getChannelExtras() == null) { + reqVO.setChannelExtras(Maps.newHashMapWithExpectedSize(1)); + } + PayWalletDO wallet = payWalletService.getOrCreateWallet(getLoginUserId(), getLoginUserType()); + reqVO.getChannelExtras().put(WalletPayClient.WALLET_ID_KEY, String.valueOf(wallet.getId())); + } + + // 2. 提交支付 + PayOrderSubmitRespVO respVO = payOrderService.submitOrder(reqVO, getClientIP()); + return success(PayOrderConvert.INSTANCE.convert3(respVO)); + } + +} diff --git a/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/app/order/vo/AppPayOrderSubmitReqVO.java b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/app/order/vo/AppPayOrderSubmitReqVO.java new file mode 100644 index 0000000..0b92d25 --- /dev/null +++ b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/app/order/vo/AppPayOrderSubmitReqVO.java @@ -0,0 +1,15 @@ +package cn.iocoder.yudao.module.pay.controller.app.order.vo; + +import cn.iocoder.yudao.module.pay.controller.admin.order.vo.PayOrderSubmitReqVO; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.experimental.Accessors; + +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import java.util.Map; + +@Schema(description = "用户 APP - 支付订单提交 Request VO") +@Data +public class AppPayOrderSubmitReqVO extends PayOrderSubmitReqVO { +} diff --git a/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/app/order/vo/AppPayOrderSubmitRespVO.java b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/app/order/vo/AppPayOrderSubmitRespVO.java new file mode 100644 index 0000000..106535e --- /dev/null +++ b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/app/order/vo/AppPayOrderSubmitRespVO.java @@ -0,0 +1,15 @@ +package cn.iocoder.yudao.module.pay.controller.app.order.vo; + +import cn.iocoder.yudao.module.pay.controller.admin.order.vo.PayOrderSubmitRespVO; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.Accessors; + +@Schema(description = "用户 APP - 支付订单提交 Response VO") +@Data +public class AppPayOrderSubmitRespVO extends PayOrderSubmitRespVO { + +} diff --git a/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/app/transfer/AppPayTransferController.java b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/app/transfer/AppPayTransferController.java new file mode 100644 index 0000000..bf8fc29 --- /dev/null +++ b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/app/transfer/AppPayTransferController.java @@ -0,0 +1,36 @@ +package cn.iocoder.yudao.module.pay.controller.app.transfer; + +import cn.iocoder.yudao.framework.common.pojo.CommonResult; +import cn.iocoder.yudao.module.pay.service.transfer.PayTransferService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; + +@Tag(name = "用户 APP - 转账单") +@RestController +@RequestMapping("/pay/transfer") +@Validated +@Slf4j +public class AppPayTransferController { + + @Resource + private PayTransferService transferService; + + @GetMapping("/sync") + @Operation(summary = "同步转账单") // 目的:解决微信转账的异步回调可能有延迟的问题 + @Parameter(name = "id", description = "编号", required = true, example = "1024") + public CommonResult syncTransfer(@RequestParam("id") Long id) { + transferService.syncTransfer(id); + return success(true); + } + +} diff --git a/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/app/wallet/AppPayWalletController.java b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/app/wallet/AppPayWalletController.java new file mode 100644 index 0000000..6d47f9a --- /dev/null +++ b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/app/wallet/AppPayWalletController.java @@ -0,0 +1,42 @@ +package cn.iocoder.yudao.module.pay.controller.app.wallet; + +import cn.iocoder.yudao.framework.common.enums.UserTypeEnum; +import cn.iocoder.yudao.framework.common.pojo.CommonResult; +import cn.iocoder.yudao.module.pay.controller.app.wallet.vo.wallet.AppPayWalletRespVO; +import cn.iocoder.yudao.module.pay.convert.wallet.PayWalletConvert; +import cn.iocoder.yudao.module.pay.dal.dataobject.wallet.PayWalletDO; +import cn.iocoder.yudao.module.pay.service.wallet.PayWalletService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.extern.slf4j.Slf4j; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import jakarta.annotation.Resource; + +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; +import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId; + +/** + * @author jason + */ +@Tag(name = "用户 APP - 钱包") +@RestController +@RequestMapping("/pay/wallet") +@Validated +@Slf4j +public class AppPayWalletController { + + @Resource + private PayWalletService payWalletService; + + @GetMapping("/get") + @Operation(summary = "获取钱包") + public CommonResult getPayWallet() { + PayWalletDO wallet = payWalletService.getOrCreateWallet(getLoginUserId(), UserTypeEnum.MEMBER.getValue()); + return success(PayWalletConvert.INSTANCE.convert(wallet)); + } + +} diff --git a/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/app/wallet/AppPayWalletRechargeController.java b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/app/wallet/AppPayWalletRechargeController.java new file mode 100644 index 0000000..109f2b3 --- /dev/null +++ b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/app/wallet/AppPayWalletRechargeController.java @@ -0,0 +1,72 @@ +package cn.iocoder.yudao.module.pay.controller.app.wallet; + +import cn.hutool.core.collection.CollUtil; +import cn.iocoder.yudao.framework.common.enums.UserTypeEnum; +import cn.iocoder.yudao.framework.common.pojo.CommonResult; +import cn.iocoder.yudao.framework.common.pojo.PageParam; +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.module.pay.controller.app.wallet.vo.recharge.AppPayWalletRechargeCreateReqVO; +import cn.iocoder.yudao.module.pay.controller.app.wallet.vo.recharge.AppPayWalletRechargeCreateRespVO; +import cn.iocoder.yudao.module.pay.controller.app.wallet.vo.recharge.AppPayWalletRechargeRespVO; +import cn.iocoder.yudao.module.pay.convert.wallet.PayWalletConvert; +import cn.iocoder.yudao.module.pay.convert.wallet.PayWalletRechargeConvert; +import cn.iocoder.yudao.module.pay.dal.dataobject.order.PayOrderDO; +import cn.iocoder.yudao.module.pay.dal.dataobject.wallet.PayWalletRechargeDO; +import cn.iocoder.yudao.module.pay.service.order.PayOrderService; +import cn.iocoder.yudao.module.pay.service.wallet.PayWalletRechargeService; +import com.google.common.collect.Lists; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.extern.slf4j.Slf4j; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import jakarta.annotation.Resource; +import jakarta.validation.Valid; + +import java.time.LocalDateTime; +import java.util.Arrays; +import java.util.List; + +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList; +import static cn.iocoder.yudao.framework.common.util.servlet.ServletUtils.getClientIP; +import static cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils.getLoginUserId; +import static cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils.getLoginUserType; + +@Tag(name = "用户 APP - 钱包充值") +@RestController +@RequestMapping("/pay/wallet-recharge") +@Validated +@Slf4j +public class AppPayWalletRechargeController { + + @Resource + private PayWalletRechargeService walletRechargeService; + @Resource + private PayOrderService payOrderService; + + @PostMapping("/create") + @Operation(summary = "创建钱包充值记录(发起充值)") + public CommonResult createWalletRecharge( + @Valid @RequestBody AppPayWalletRechargeCreateReqVO reqVO) { + PayWalletRechargeDO walletRecharge = walletRechargeService.createWalletRecharge( + getLoginUserId(), getLoginUserType(), getClientIP(), reqVO); + return success(PayWalletRechargeConvert.INSTANCE.convert(walletRecharge)); + } + + @GetMapping("/page") + @Operation(summary = "获得钱包充值记录分页") + public CommonResult> getWalletRechargePage(@Valid PageParam pageReqVO) { + PageResult pageResult = walletRechargeService.getWalletRechargePackagePage( + getLoginUserId(), UserTypeEnum.MEMBER.getValue(), pageReqVO, true); + if (CollUtil.isEmpty(pageResult.getList())) { + return success(PageResult.empty(pageResult.getTotal())); + } + // 拼接数据 + List payOrderList = payOrderService.getOrderList( + convertList(pageResult.getList(), PayWalletRechargeDO::getPayOrderId)); + return success(PayWalletRechargeConvert.INSTANCE.convertPage(pageResult, payOrderList)); + } + +} diff --git a/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/app/wallet/AppPayWalletRechargePackageController.java b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/app/wallet/AppPayWalletRechargePackageController.java new file mode 100644 index 0000000..bab3f86 --- /dev/null +++ b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/app/wallet/AppPayWalletRechargePackageController.java @@ -0,0 +1,42 @@ +package cn.iocoder.yudao.module.pay.controller.app.wallet; + +import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; +import cn.iocoder.yudao.framework.common.pojo.CommonResult; +import cn.iocoder.yudao.framework.common.util.object.BeanUtils; +import cn.iocoder.yudao.module.pay.controller.app.wallet.vo.recharge.AppPayWalletPackageRespVO; +import cn.iocoder.yudao.module.pay.dal.dataobject.wallet.PayWalletRechargePackageDO; +import cn.iocoder.yudao.module.pay.service.wallet.PayWalletRechargePackageService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.util.Comparator; +import java.util.List; + +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; + +@Tag(name = "用户 APP - 钱包充值套餐") +@RestController +@RequestMapping("/pay/wallet-recharge-package") +@Validated +@Slf4j +public class AppPayWalletRechargePackageController { + + @Resource + private PayWalletRechargePackageService walletRechargePackageService; + + @GetMapping("/list") + @Operation(summary = "获得钱包充值套餐列表") + public CommonResult> getWalletRechargePackageList() { + List list = walletRechargePackageService.getWalletRechargePackageList( + CommonStatusEnum.ENABLE.getStatus()); + list.sort(Comparator.comparingInt(PayWalletRechargePackageDO::getPayPrice)); + return success(BeanUtils.toBean(list, AppPayWalletPackageRespVO.class)); + } + +} diff --git a/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/app/wallet/AppPayWalletTransactionController.java b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/app/wallet/AppPayWalletTransactionController.java new file mode 100644 index 0000000..463709e --- /dev/null +++ b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/app/wallet/AppPayWalletTransactionController.java @@ -0,0 +1,61 @@ +package cn.iocoder.yudao.module.pay.controller.app.wallet; + +import cn.iocoder.yudao.framework.common.enums.UserTypeEnum; +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.module.pay.controller.app.wallet.vo.transaction.AppPayWalletTransactionSummaryRespVO; +import cn.iocoder.yudao.module.pay.controller.app.wallet.vo.transaction.AppPayWalletTransactionPageReqVO; +import cn.iocoder.yudao.module.pay.controller.app.wallet.vo.transaction.AppPayWalletTransactionRespVO; +import cn.iocoder.yudao.module.pay.dal.dataobject.wallet.PayWalletTransactionDO; +import cn.iocoder.yudao.module.pay.service.wallet.PayWalletTransactionService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.extern.slf4j.Slf4j; +import org.springframework.format.annotation.DateTimeFormat; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import jakarta.annotation.Resource; +import jakarta.validation.Valid; + +import java.time.LocalDateTime; + +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; +import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND; +import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId; + +@Tag(name = "用户 APP - 钱包余额明细") +@RestController +@RequestMapping("/pay/wallet-transaction") +@Validated +@Slf4j +public class AppPayWalletTransactionController { + + @Resource + private PayWalletTransactionService payWalletTransactionService; + + @GetMapping("/page") + @Operation(summary = "获得钱包流水分页") + public CommonResult> getWalletTransactionPage( + @Valid AppPayWalletTransactionPageReqVO pageReqVO) { + PageResult pageResult = payWalletTransactionService.getWalletTransactionPage( + getLoginUserId(), UserTypeEnum.MEMBER.getValue(), pageReqVO); + return success(BeanUtils.toBean(pageResult, AppPayWalletTransactionRespVO.class)); + } + + @GetMapping("/get-summary") + @Operation(summary = "获得钱包流水统计") + @Parameter(name = "times", description = "时间段", required = true) + public CommonResult getWalletTransactionSummary( + @RequestParam("createTime") @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) LocalDateTime[] createTime) { + AppPayWalletTransactionSummaryRespVO summary = payWalletTransactionService.getWalletTransactionSummary( + getLoginUserId(), UserTypeEnum.MEMBER.getValue(), createTime); + return success(summary); + } + +} diff --git a/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/app/wallet/vo/recharge/AppPayWalletPackageRespVO.java b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/app/wallet/vo/recharge/AppPayWalletPackageRespVO.java new file mode 100644 index 0000000..c12db98 --- /dev/null +++ b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/app/wallet/vo/recharge/AppPayWalletPackageRespVO.java @@ -0,0 +1,20 @@ +package cn.iocoder.yudao.module.pay.controller.app.wallet.vo.recharge; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +@Schema(description = "用户 APP - 用户充值套餐 Response VO") +@Data +public class AppPayWalletPackageRespVO { + + @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") + private Long id; + @Schema(description = "套餐名", requiredMode = Schema.RequiredMode.REQUIRED, example = "小套餐") + private String name; + + @Schema(description = "支付金额", requiredMode = Schema.RequiredMode.REQUIRED, example = "10") + private Integer payPrice; + @Schema(description = "赠送金额", requiredMode = Schema.RequiredMode.REQUIRED, example = "20") + private Integer bonusPrice; + +} diff --git a/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/app/wallet/vo/recharge/AppPayWalletRechargeCreateReqVO.java b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/app/wallet/vo/recharge/AppPayWalletRechargeCreateReqVO.java new file mode 100644 index 0000000..48a83ec --- /dev/null +++ b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/app/wallet/vo/recharge/AppPayWalletRechargeCreateReqVO.java @@ -0,0 +1,26 @@ +package cn.iocoder.yudao.module.pay.controller.app.wallet.vo.recharge; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import jakarta.validation.constraints.AssertTrue; +import jakarta.validation.constraints.Min; +import java.util.Objects; + +@Schema(description = "用户 APP - 创建钱包充值 Request VO") +@Data +public class AppPayWalletRechargeCreateReqVO { + + @Schema(description = "支付金额", example = "1000") + @Min(value = 1, message = "支付金额必须大于零") + private Integer payPrice; + + @Schema(description = "充值套餐编号", example = "1024") + private Long packageId; + + @AssertTrue(message = "充值金额和充钱套餐不能同时为空") + public boolean isValidPayPriceAndPackageId() { + return Objects.nonNull(payPrice) || Objects.nonNull(packageId); + } + +} diff --git a/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/app/wallet/vo/recharge/AppPayWalletRechargeCreateRespVO.java b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/app/wallet/vo/recharge/AppPayWalletRechargeCreateRespVO.java new file mode 100644 index 0000000..2c4a96f --- /dev/null +++ b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/app/wallet/vo/recharge/AppPayWalletRechargeCreateRespVO.java @@ -0,0 +1,16 @@ +package cn.iocoder.yudao.module.pay.controller.app.wallet.vo.recharge; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +@Schema(description = "用户 APP - 创建钱包充值 Resp VO") +@Data +public class AppPayWalletRechargeCreateRespVO { + + @Schema(description = "钱包充值编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + private Long id; + + @Schema(description = "支付订单编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "100") + private Long payOrderId; + +} diff --git a/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/app/wallet/vo/recharge/AppPayWalletRechargeRespVO.java b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/app/wallet/vo/recharge/AppPayWalletRechargeRespVO.java new file mode 100644 index 0000000..ee55b40 --- /dev/null +++ b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/app/wallet/vo/recharge/AppPayWalletRechargeRespVO.java @@ -0,0 +1,42 @@ +package cn.iocoder.yudao.module.pay.controller.app.wallet.vo.recharge; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.time.LocalDateTime; + +@Schema(description = "用户 APP - 钱包充值记录 Resp VO") +@Data +public class AppPayWalletRechargeRespVO { + + @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + private Long id; + + @Schema(description = "用户实际到账余额", requiredMode = Schema.RequiredMode.REQUIRED, example = "100") + private Integer totalPrice; + + @Schema(description = "实际支付金额", requiredMode = Schema.RequiredMode.REQUIRED, example = "20") + private Integer payPrice; + + @Schema(description = "钱包赠送金额", requiredMode = Schema.RequiredMode.REQUIRED, example = "80") + private Integer bonusPrice; + + @Schema(description = "支付成功的支付渠道", requiredMode = Schema.RequiredMode.REQUIRED) + private String payChannelCode; + + @Schema(description = "支付渠道名", example = "微信小程序支付") + private String payChannelName; + + @Schema(description = "支付订单编号", requiredMode = Schema.RequiredMode.REQUIRED) + private Long payOrderId; + + @Schema(description = "支付成功的外部订单号", requiredMode = Schema.RequiredMode.REQUIRED) + private String payOrderChannelOrderNo; // 从 PayOrderDO 的 channelOrderNo 字段 + + @Schema(description = "订单支付时间", requiredMode = Schema.RequiredMode.REQUIRED) + private LocalDateTime payTime; + + @Schema(description = "退款状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "0") + private Integer refundStatus; + +} diff --git a/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/app/wallet/vo/transaction/AppPayWalletTransactionPageReqVO.java b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/app/wallet/vo/transaction/AppPayWalletTransactionPageReqVO.java new file mode 100644 index 0000000..d5daad0 --- /dev/null +++ b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/app/wallet/vo/transaction/AppPayWalletTransactionPageReqVO.java @@ -0,0 +1,31 @@ +package cn.iocoder.yudao.module.pay.controller.app.wallet.vo.transaction; + +import cn.iocoder.yudao.framework.common.pojo.PageParam; +import cn.iocoder.yudao.framework.common.util.date.DateUtils; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import org.springframework.format.annotation.DateTimeFormat; + +import java.time.LocalDateTime; + +@Schema(description = "用户 APP - 钱包流水分页 Request VO") +@Data +public class AppPayWalletTransactionPageReqVO extends PageParam { + + /** + * 类型 - 收入 + */ + public static final Integer TYPE_INCOME = 1; + /** + * 类型 - 支出 + */ + public static final Integer TYPE_EXPENSE = 2; + + @Schema(description = "类型", example = "1") + private Integer type; + + @Schema(description = "创建时间") + @DateTimeFormat(pattern = DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) + private LocalDateTime[] createTime; + +} diff --git a/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/app/wallet/vo/transaction/AppPayWalletTransactionRespVO.java b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/app/wallet/vo/transaction/AppPayWalletTransactionRespVO.java new file mode 100644 index 0000000..5c20188 --- /dev/null +++ b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/app/wallet/vo/transaction/AppPayWalletTransactionRespVO.java @@ -0,0 +1,24 @@ +package cn.iocoder.yudao.module.pay.controller.app.wallet.vo.transaction; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.time.LocalDateTime; + +@Schema(description = "用户 APP - 钱包流水分页 Response VO") +@Data +public class AppPayWalletTransactionRespVO { + + @Schema(description = "业务分类", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + private Integer bizType; + + @Schema(description = "交易金额,单位分", requiredMode = Schema.RequiredMode.REQUIRED, example = "100") + private Long price; + + @Schema(description = "流水标题", requiredMode = Schema.RequiredMode.REQUIRED, example = "土豆土豆") + private String title; + + @Schema(description = "交易时间", requiredMode = Schema.RequiredMode.REQUIRED) + private LocalDateTime createTime; + +} diff --git a/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/app/wallet/vo/transaction/AppPayWalletTransactionSummaryRespVO.java b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/app/wallet/vo/transaction/AppPayWalletTransactionSummaryRespVO.java new file mode 100644 index 0000000..b1cc587 --- /dev/null +++ b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/app/wallet/vo/transaction/AppPayWalletTransactionSummaryRespVO.java @@ -0,0 +1,16 @@ +package cn.iocoder.yudao.module.pay.controller.app.wallet.vo.transaction; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +@Schema(description = "用户 APP - 钱包流水统计 Request VO") +@Data +public class AppPayWalletTransactionSummaryRespVO { + + @Schema(description = "累计支出,单位分", requiredMode = Schema.RequiredMode.REQUIRED, example = "1000") + private Integer totalExpense; + + @Schema(description = "累计收入,单位分", requiredMode = Schema.RequiredMode.REQUIRED, example = "2000") + private Integer totalIncome; + +} diff --git a/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/app/wallet/vo/wallet/AppPayWalletRespVO.java b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/app/wallet/vo/wallet/AppPayWalletRespVO.java new file mode 100644 index 0000000..7e42fc5 --- /dev/null +++ b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/app/wallet/vo/wallet/AppPayWalletRespVO.java @@ -0,0 +1,19 @@ +package cn.iocoder.yudao.module.pay.controller.app.wallet.vo.wallet; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +@Schema(description = "用户 APP - 用户钱包 Response VO") +@Data +public class AppPayWalletRespVO { + + @Schema(description = "钱包余额,单位分", requiredMode = Schema.RequiredMode.REQUIRED, example = "100") + private Integer balance; + + @Schema(description = "累计支出,单位分", requiredMode = Schema.RequiredMode.REQUIRED, example = "1000") + private Integer totalExpense; + + @Schema(description = "累计充值,单位分", requiredMode = Schema.RequiredMode.REQUIRED, example = "2000") + private Integer totalRecharge; + +} diff --git a/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/package-info.java b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/package-info.java new file mode 100644 index 0000000..6521227 --- /dev/null +++ b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/package-info.java @@ -0,0 +1,6 @@ +/** + * 提供 RESTful API 给前端: + * 1. admin 包:提供给管理后台 yudao-ui-admin 前端项目 + * 2. app 包:提供给用户 APP yudao-ui-app 前端项目,它的 Controller 和 VO 都要添加 App 前缀,用于和管理后台进行区分 + */ +package cn.iocoder.yudao.module.pay.controller; diff --git a/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/convert/app/PayAppConvert.java b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/convert/app/PayAppConvert.java new file mode 100644 index 0000000..4853d07 --- /dev/null +++ b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/convert/app/PayAppConvert.java @@ -0,0 +1,49 @@ +package cn.iocoder.yudao.module.pay.convert.app; + +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; +import cn.iocoder.yudao.module.pay.controller.admin.app.vo.PayAppCreateReqVO; +import cn.iocoder.yudao.module.pay.controller.admin.app.vo.PayAppPageItemRespVO; +import cn.iocoder.yudao.module.pay.controller.admin.app.vo.PayAppRespVO; +import cn.iocoder.yudao.module.pay.controller.admin.app.vo.PayAppUpdateReqVO; +import cn.iocoder.yudao.module.pay.dal.dataobject.app.PayAppDO; +import cn.iocoder.yudao.module.pay.dal.dataobject.channel.PayChannelDO; +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.function.Consumer; + +/** + * 支付应用信息 Convert + * + * @author 芋艿 + */ +@Mapper +public interface PayAppConvert { + + PayAppConvert INSTANCE = Mappers.getMapper(PayAppConvert.class); + + PayAppPageItemRespVO pageConvert (PayAppDO bean); + + PayAppDO convert(PayAppCreateReqVO bean); + + PayAppDO convert(PayAppUpdateReqVO bean); + + PayAppRespVO convert(PayAppDO bean); + + List convertList(List list); + + PageResult convertPage(PageResult page); + + default PageResult convertPage(PageResult pageResult, List channels) { + PageResult voPageResult = convertPage(pageResult); + // 处理 channel 关系 + Map> appIdChannelMap = CollectionUtils.convertMultiMap2(channels, PayChannelDO::getAppId, PayChannelDO::getCode); + voPageResult.getList().forEach(app -> app.setChannelCodes(appIdChannelMap.get(app.getId()))); + return voPageResult; + } + +} diff --git a/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/convert/channel/PayChannelConvert.java b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/convert/channel/PayChannelConvert.java new file mode 100644 index 0000000..5baf598 --- /dev/null +++ b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/convert/channel/PayChannelConvert.java @@ -0,0 +1,28 @@ +package cn.iocoder.yudao.module.pay.convert.channel; + +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.module.pay.controller.admin.channel.vo.PayChannelCreateReqVO; +import cn.iocoder.yudao.module.pay.controller.admin.channel.vo.PayChannelRespVO; +import cn.iocoder.yudao.module.pay.controller.admin.channel.vo.PayChannelUpdateReqVO; +import cn.iocoder.yudao.module.pay.dal.dataobject.channel.PayChannelDO; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.factory.Mappers; + +@Mapper +public interface PayChannelConvert { + + PayChannelConvert INSTANCE = Mappers.getMapper(PayChannelConvert.class); + + @Mapping(target = "config",ignore = true) + PayChannelDO convert(PayChannelCreateReqVO bean); + + @Mapping(target = "config",ignore = true) + PayChannelDO convert(PayChannelUpdateReqVO bean); + + @Mapping(target = "config",expression = "java(cn.iocoder.yudao.framework.common.util.json.JsonUtils.toJsonString(bean.getConfig()))") + PayChannelRespVO convert(PayChannelDO bean); + + PageResult convertPage(PageResult page); + +} diff --git a/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/convert/order/PayOrderConvert.java b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/convert/order/PayOrderConvert.java new file mode 100755 index 0000000..8321de3 --- /dev/null +++ b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/convert/order/PayOrderConvert.java @@ -0,0 +1,74 @@ +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.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.*; +import cn.iocoder.yudao.module.pay.controller.app.order.vo.AppPayOrderSubmitRespVO; +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 org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.factory.Mappers; + +import java.util.List; +import java.util.Map; + +/** + * 支付订单 Convert + * + * @author aquan + */ +@Mapper +public interface PayOrderConvert { + + PayOrderConvert INSTANCE = Mappers.getMapper(PayOrderConvert.class); + + PayOrderRespVO convert(PayOrderDO bean); + + PayOrderRespDTO convert2(PayOrderDO order); + + default PayOrderDetailsRespVO convert(PayOrderDO order, PayOrderExtensionDO orderExtension, PayAppDO app) { + PayOrderDetailsRespVO respVO = convertDetail(order); + respVO.setExtension(convert(orderExtension)); + if (app != null) { + respVO.setAppName(app.getName()); + } + return respVO; + } + PayOrderDetailsRespVO convertDetail(PayOrderDO bean); + PayOrderDetailsRespVO.PayOrderExtension convert(PayOrderExtensionDO bean); + + default PageResult convertPage(PageResult page, Map appMap) { + PageResult result = convertPage(page); + result.getList().forEach(order -> MapUtils.findAndThen(appMap, order.getAppId(), app -> order.setAppName(app.getName()))); + return result; + } + PageResult convertPage(PageResult page); + + default List convertList(List list, Map appMap) { + return CollectionUtils.convertList(list, order -> { + PayOrderExcelVO excelVO = convertExcel(order); + MapUtils.findAndThen(appMap, order.getAppId(), app -> excelVO.setAppName(app.getName())); + return excelVO; + }); + } + PayOrderExcelVO convertExcel(PayOrderDO bean); + + PayOrderDO convert(PayOrderCreateReqDTO bean); + + @Mapping(target = "id", ignore = true) + PayOrderExtensionDO convert(PayOrderSubmitReqVO bean, String userIp); + + PayOrderUnifiedReqDTO convert2(PayOrderSubmitReqVO reqVO, String userIp); + + @Mapping(source = "order.status", target = "status") + 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/src/main/java/cn/iocoder/yudao/module/pay/convert/package-info.java b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/convert/package-info.java new file mode 100644 index 0000000..df43d5a --- /dev/null +++ b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/convert/package-info.java @@ -0,0 +1,6 @@ +/** + * 提供 POJO 类的实体转换 + * + * 目前使用 MapStruct 框架 + */ +package cn.iocoder.yudao.module.pay.convert; diff --git a/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/convert/refund/PayRefundConvert.java b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/convert/refund/PayRefundConvert.java new file mode 100755 index 0000000..c94aa00 --- /dev/null +++ b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/convert/refund/PayRefundConvert.java @@ -0,0 +1,53 @@ +package cn.iocoder.yudao.module.pay.convert.refund; + +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.module.pay.api.refund.dto.PayRefundCreateReqDTO; +import cn.iocoder.yudao.module.pay.controller.admin.refund.vo.PayRefundDetailsRespVO; +import cn.iocoder.yudao.module.pay.controller.admin.refund.vo.PayRefundExcelVO; +import cn.iocoder.yudao.module.pay.controller.admin.refund.vo.PayRefundPageItemRespVO; +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.refund.PayRefundDO; +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +import java.util.List; +import java.util.Map; + +@Mapper +public interface PayRefundConvert { + + PayRefundConvert INSTANCE = Mappers.getMapper(PayRefundConvert.class); + + + default PayRefundDetailsRespVO convert(PayRefundDO refund, PayAppDO app) { + PayRefundDetailsRespVO respVO = convert(refund); + if (app != null) { + respVO.setAppName(app.getName()); + } + return respVO; + } + PayRefundDetailsRespVO convert(PayRefundDO bean); + PayRefundDetailsRespVO.Order convert(PayOrderDO bean); + + default PageResult convertPage(PageResult page, Map appMap) { + PageResult result = convertPage(page); + result.getList().forEach(order -> MapUtils.findAndThen(appMap, order.getAppId(), app -> order.setAppName(app.getName()))); + return result; + } + PageResult convertPage(PageResult page); + + PayRefundDO convert(PayRefundCreateReqDTO bean); + + default List convertList(List list, Map appMap) { + return CollectionUtils.convertList(list, order -> { + PayRefundExcelVO excelVO = convertExcel(order); + MapUtils.findAndThen(appMap, order.getAppId(), app -> excelVO.setAppName(app.getName())); + return excelVO; + }); + } + PayRefundExcelVO convertExcel(PayRefundDO bean); + +} diff --git a/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/convert/wallet/PayWalletConvert.java b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/convert/wallet/PayWalletConvert.java new file mode 100644 index 0000000..3617f23 --- /dev/null +++ b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/convert/wallet/PayWalletConvert.java @@ -0,0 +1,21 @@ +package cn.iocoder.yudao.module.pay.convert.wallet; + +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.module.pay.controller.admin.wallet.vo.wallet.PayWalletRespVO; +import cn.iocoder.yudao.module.pay.controller.app.wallet.vo.wallet.AppPayWalletRespVO; +import cn.iocoder.yudao.module.pay.dal.dataobject.wallet.PayWalletDO; +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +@Mapper +public interface PayWalletConvert { + + PayWalletConvert INSTANCE = Mappers.getMapper(PayWalletConvert.class); + + AppPayWalletRespVO convert(PayWalletDO bean); + + PayWalletRespVO convert02(PayWalletDO bean); + + PageResult convertPage(PageResult page); + +} diff --git a/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/convert/wallet/PayWalletRechargeConvert.java b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/convert/wallet/PayWalletRechargeConvert.java new file mode 100644 index 0000000..2dda1d1 --- /dev/null +++ b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/convert/wallet/PayWalletRechargeConvert.java @@ -0,0 +1,43 @@ +package cn.iocoder.yudao.module.pay.convert.wallet; + +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.common.util.object.BeanUtils; +import cn.iocoder.yudao.framework.dict.core.DictFrameworkUtils; +import cn.iocoder.yudao.module.pay.controller.app.wallet.vo.recharge.AppPayWalletRechargeCreateRespVO; +import cn.iocoder.yudao.module.pay.controller.app.wallet.vo.recharge.AppPayWalletRechargeRespVO; +import cn.iocoder.yudao.module.pay.dal.dataobject.order.PayOrderDO; +import cn.iocoder.yudao.module.pay.dal.dataobject.wallet.PayWalletRechargeDO; +import cn.iocoder.yudao.module.pay.enums.DictTypeConstants; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.factory.Mappers; + +import java.util.List; +import java.util.Map; + +@Mapper +public interface PayWalletRechargeConvert { + + PayWalletRechargeConvert INSTANCE = Mappers.getMapper(PayWalletRechargeConvert.class); + + @Mapping(target = "totalPrice", expression = "java( payPrice + bonusPrice)") + PayWalletRechargeDO convert(Long walletId, Integer payPrice, Integer bonusPrice, Long packageId); + + AppPayWalletRechargeCreateRespVO convert(PayWalletRechargeDO bean); + + default PageResult convertPage(PageResult pageResult, + List payOrderList) { + PageResult voPageResult = BeanUtils.toBean(pageResult, AppPayWalletRechargeRespVO.class); + Map payOrderMap = CollectionUtils.convertMap(payOrderList, PayOrderDO::getId); + voPageResult.getList().forEach(recharge -> { + recharge.setPayChannelName(DictFrameworkUtils.parseDictDataLabel( + DictTypeConstants.CHANNEL_CODE, recharge.getPayChannelCode())); + MapUtils.findAndThen(payOrderMap, recharge.getPayOrderId(), + order -> recharge.setPayOrderChannelOrderNo(order.getChannelOrderNo())); + }); + return voPageResult; + } + +} diff --git a/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/convert/wallet/PayWalletRechargePackageConvert.java b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/convert/wallet/PayWalletRechargePackageConvert.java new file mode 100644 index 0000000..d3af67b --- /dev/null +++ b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/convert/wallet/PayWalletRechargePackageConvert.java @@ -0,0 +1,29 @@ +package cn.iocoder.yudao.module.pay.convert.wallet; + +import java.util.*; + +import cn.iocoder.yudao.framework.common.pojo.PageResult; + +import cn.iocoder.yudao.module.pay.controller.admin.wallet.vo.rechargepackage.WalletRechargePackageCreateReqVO; +import cn.iocoder.yudao.module.pay.controller.admin.wallet.vo.rechargepackage.WalletRechargePackageRespVO; +import cn.iocoder.yudao.module.pay.controller.admin.wallet.vo.rechargepackage.WalletRechargePackageUpdateReqVO; +import cn.iocoder.yudao.module.pay.dal.dataobject.wallet.PayWalletRechargePackageDO; +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +@Mapper +public interface PayWalletRechargePackageConvert { + + PayWalletRechargePackageConvert INSTANCE = Mappers.getMapper(PayWalletRechargePackageConvert.class); + + PayWalletRechargePackageDO convert(WalletRechargePackageCreateReqVO bean); + + PayWalletRechargePackageDO convert(WalletRechargePackageUpdateReqVO bean); + + WalletRechargePackageRespVO convert(PayWalletRechargePackageDO bean); + + List convertList(List list); + + PageResult convertPage(PageResult page); + +} diff --git a/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/convert/wallet/PayWalletTransactionConvert.java b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/convert/wallet/PayWalletTransactionConvert.java new file mode 100644 index 0000000..a64c217 --- /dev/null +++ b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/convert/wallet/PayWalletTransactionConvert.java @@ -0,0 +1,20 @@ +package cn.iocoder.yudao.module.pay.convert.wallet; + +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.module.pay.controller.admin.wallet.vo.transaction.PayWalletTransactionRespVO; +import cn.iocoder.yudao.module.pay.controller.app.wallet.vo.transaction.AppPayWalletTransactionRespVO; +import cn.iocoder.yudao.module.pay.dal.dataobject.wallet.PayWalletTransactionDO; +import cn.iocoder.yudao.module.pay.service.wallet.bo.WalletTransactionCreateReqBO; +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +@Mapper +public interface PayWalletTransactionConvert { + + PayWalletTransactionConvert INSTANCE = Mappers.getMapper(PayWalletTransactionConvert.class); + + PageResult convertPage2(PageResult page); + + PayWalletTransactionDO convert(WalletTransactionCreateReqBO bean); + +} diff --git a/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/convert/《芋道 Spring Boot 对象转换 MapStruct 入门》.md b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/convert/《芋道 Spring Boot 对象转换 MapStruct 入门》.md new file mode 100644 index 0000000..8153487 --- /dev/null +++ b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/convert/《芋道 Spring Boot 对象转换 MapStruct 入门》.md @@ -0,0 +1 @@ + diff --git a/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/dal/dataobject/app/PayAppDO.java b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/dal/dataobject/app/PayAppDO.java new file mode 100644 index 0000000..456a40a --- /dev/null +++ b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/dal/dataobject/app/PayAppDO.java @@ -0,0 +1,66 @@ +package cn.iocoder.yudao.module.pay.dal.dataobject.app; + +import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; +import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; +import com.baomidou.mybatisplus.annotation.KeySequence; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.*; + +/** + * 支付应用 DO + * 一个商户下,可能会有多个支付应用。例如说,京东有京东商城、京东到家等等 + * 不过一般来说,一个商户,只有一个应用哈~ + * + * 即 PayMerchantDO : PayAppDO = 1 : n + * + * @author 芋道源码 + */ +@TableName("pay_app") +@KeySequence("pay_app_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class PayAppDO extends BaseDO { + + /** + * 应用编号,数据库自增 + */ + @TableId + private Long id; + /** + * 应用标识 + */ + private String appKey; + /** + * 应用名 + */ + private String name; + /** + * 状态 + * + * 枚举 {@link CommonStatusEnum} + */ + private Integer status; + /** + * 备注 + */ + private String remark; + /** + * 支付结果的回调地址 + */ + private String orderNotifyUrl; + /** + * 退款结果的回调地址 + */ + private String refundNotifyUrl; + + /** + * 转账结果的回调地址 + */ + private String transferNotifyUrl; + +} diff --git a/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/dal/dataobject/channel/PayChannelDO.java b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/dal/dataobject/channel/PayChannelDO.java new file mode 100644 index 0000000..0039786 --- /dev/null +++ b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/dal/dataobject/channel/PayChannelDO.java @@ -0,0 +1,118 @@ +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.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 + * 一个应用下,会有多种支付渠道,例如说微信支付、支付宝支付等等 + * + * 即 PayAppDO : PayChannelDO = 1 : n + * + * @author 芋道源码 + */ +@TableName(value = "pay_channel", autoResultMap = true) +@KeySequence("pay_channel_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class PayChannelDO extends TenantBaseDO { + + /** + * 渠道编号,数据库自增 + */ + private Long id; + /** + * 渠道编码 + * + * 枚举 {@link PayChannelEnum} + */ + private String code; + /** + * 状态 + * + * 枚举 {@link CommonStatusEnum} + */ + private Integer status; + /** + * 渠道费率,单位:百分比 + */ + private Double feeRate; + /** + * 备注 + */ + private String remark; + + /** + * 应用编号 + * + * 关联 {@link PayAppDO#getId()} + */ + private Long appId; + /** + * 支付渠道配置 + */ + @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/src/main/java/cn/iocoder/yudao/module/pay/dal/dataobject/demo/PayDemoOrderDO.java b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/dal/dataobject/demo/PayDemoOrderDO.java new file mode 100644 index 0000000..a22f7c1 --- /dev/null +++ b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/dal/dataobject/demo/PayDemoOrderDO.java @@ -0,0 +1,87 @@ +package cn.iocoder.yudao.module.pay.dal.dataobject.demo; + +import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; +import com.baomidou.mybatisplus.annotation.KeySequence; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.*; + +import java.time.LocalDateTime; + +/** + * 示例订单 + * + * 演示业务系统的订单,如何接入 pay 系统的支付与退款 + * + * @author 芋道源码 + */ +@TableName("pay_demo_order") +@KeySequence("pay_demo_order_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class PayDemoOrderDO extends BaseDO { + + /** + * 订单编号,自增 + */ + @TableId + private Long id; + /** + * 用户编号 + */ + private Long userId; + /** + * 商品编号 + */ + private Long spuId; + /** + * 商品名称 + */ + private String spuName; + /** + * 价格,单位:分 + */ + private Integer price; + + // ========== 支付相关字段 ========== + + /** + * 是否支付 + */ + private Boolean payStatus; + /** + * 支付订单编号 + * + * 对接 pay-module-biz 支付服务的支付订单编号,即 PayOrderDO 的 id 编号 + */ + private Long payOrderId; + /** + * 付款时间 + */ + private LocalDateTime payTime; + /** + * 支付渠道 + * + * 对应 PayChannelEnum 枚举 + */ + private String payChannelCode; + + // ========== 退款相关字段 ========== + /** + * 支付退款单号 + */ + private Long payRefundId; + /** + * 退款金额,单位:分 + */ + private Integer refundPrice; + /** + * 退款完成时间 + */ + private LocalDateTime refundTime; + +} diff --git a/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/dal/dataobject/demo/PayDemoWithdrawDO.java b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/dal/dataobject/demo/PayDemoWithdrawDO.java new file mode 100644 index 0000000..71d803c --- /dev/null +++ b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/dal/dataobject/demo/PayDemoWithdrawDO.java @@ -0,0 +1,84 @@ +package cn.iocoder.yudao.module.pay.dal.dataobject.demo; + +import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; +import cn.iocoder.yudao.module.pay.dal.dataobject.transfer.PayTransferDO; +import cn.iocoder.yudao.module.pay.enums.demo.PayDemoWithdrawStatusEnum; +import cn.iocoder.yudao.module.pay.enums.demo.PayDemoWithdrawTypeEnum; +import com.baomidou.mybatisplus.annotation.KeySequence; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * 示例提现订单 + * + * 演示业务系统的转账业务 + */ +@TableName(value ="pay_demo_withdraw", autoResultMap = true) +@KeySequence("pay_demo_withdraw_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 +@Data +public class PayDemoWithdrawDO extends BaseDO { + + /** + * 提现单编号,自增 + */ + @TableId + private Long id; + + /** + * 提现标题 + */ + private String subject; + /** + * 提现金额,单位:分 + */ + private Integer price; + + /** + * 收款人账号 + */ + private String userAccount; + /** + * 收款人姓名 + */ + private String userName; + + /** + * 提现方式 + * + * 枚举 {@link PayDemoWithdrawTypeEnum} + */ + private Integer type; + /** + * 提现状态 + * + * 枚举 {@link PayDemoWithdrawStatusEnum} + */ + private Integer status; + + // ========== 转账相关字段 ========== + + /** + * 转账单编号 + * + * 关联 {@link PayTransferDO#getId()} + */ + private Long payTransferId; + /** + * 转账渠道 + * + * 枚举 {@link cn.iocoder.yudao.module.pay.enums.PayChannelEnum} + */ + private String transferChannelCode; + /** + * 转账成功时间 + */ + private LocalDateTime transferTime; + /** + * 转账错误提示 + */ + private String transferErrorMsg; + +} \ No newline at end of file diff --git a/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/dal/dataobject/notify/PayNotifyLogDO.java b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/dal/dataobject/notify/PayNotifyLogDO.java new file mode 100644 index 0000000..3846283 --- /dev/null +++ b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/dal/dataobject/notify/PayNotifyLogDO.java @@ -0,0 +1,51 @@ +package cn.iocoder.yudao.module.pay.dal.dataobject.notify; + +import cn.iocoder.yudao.module.pay.enums.notify.PayNotifyStatusEnum; +import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; +import com.baomidou.mybatisplus.annotation.KeySequence; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.*; + +/** + * 商户支付、退款等的通知 Log + * 每次通知时,都会在该表中,记录一次 Log,方便排查问题 + * + * @author 芋道源码 + */ +@TableName("pay_notify_log") +@KeySequence("pay_notify_log_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 +@Data +@EqualsAndHashCode(callSuper = true) +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class PayNotifyLogDO extends BaseDO { + + /** + * 日志编号,自增 + */ + private Long id; + /** + * 通知任务编号 + * + * 关联 {@link PayNotifyTaskDO#getId()} + */ + private Long taskId; + /** + * 第几次被通知 + * + * 对应到 {@link PayNotifyTaskDO#getNotifyTimes()} + */ + private Integer notifyTimes; + /** + * HTTP 响应结果 + */ + private String response; + /** + * 支付通知状态 + * + * 枚举 {@link PayNotifyStatusEnum} + */ + private Integer status; + +} diff --git a/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/dal/dataobject/notify/PayNotifyTaskDO.java b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/dal/dataobject/notify/PayNotifyTaskDO.java new file mode 100644 index 0000000..92f1c8c --- /dev/null +++ b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/dal/dataobject/notify/PayNotifyTaskDO.java @@ -0,0 +1,106 @@ +package cn.iocoder.yudao.module.pay.dal.dataobject.notify; + +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.dal.dataobject.order.PayOrderDO; +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.enums.notify.PayNotifyStatusEnum; +import cn.iocoder.yudao.module.pay.enums.notify.PayNotifyTypeEnum; +import com.baomidou.mybatisplus.annotation.KeySequence; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.experimental.Accessors; + +import java.time.LocalDateTime; + +/** + * 支付通知 + * 在支付系统收到支付渠道的支付、退款的结果后,需要不断的通知到业务系统,直到成功。 + * + * @author 芋道源码 + */ +@TableName("pay_notify_task") +@KeySequence("pay_notify_task_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 +@Data +@EqualsAndHashCode(callSuper = true) +@Accessors(chain = true) +public class PayNotifyTaskDO extends TenantBaseDO { + + /** + * 通知频率,单位为秒。 + * + * 算上首次的通知,实际是一共 1 + 8 = 9 次。 + */ + public static final Integer[] NOTIFY_FREQUENCY = new Integer[]{ + 15, 15, 30, 180, + 1800, 1800, 1800, 3600 + }; + + /** + * 编号,自增 + */ + @TableId + private Long id; + /** + * 应用编号 + * + * 关联 {@link PayAppDO#getId()} + */ + private Long appId; + /** + * 通知类型 + * + * 枚举 {@link PayNotifyTypeEnum} + */ + private Integer type; + /** + * 数据编号,根据不同 type 进行关联: + * + * 1. {@link PayNotifyTypeEnum#ORDER} 时,关联 {@link PayOrderDO#getId()} + * 2. {@link PayNotifyTypeEnum#REFUND} 时,关联 {@link PayRefundDO#getId()} + * 3. {@link PayNotifyTypeEnum#TRANSFER} 时,关联 {@link PayTransferDO#getId()} + */ + private Long dataId; + /** + * 商户订单编号 + */ + private String merchantOrderId; + /** + * 商户退款编号 + */ + private String merchantRefundId; + /** + * 商户转账编号 + */ + private String merchantTransferId; + /** + * 通知状态 + * + * 枚举 {@link PayNotifyStatusEnum} + */ + private Integer status; + /** + * 下一次通知时间 + */ + private LocalDateTime nextNotifyTime; + /** + * 最后一次执行时间 + */ + private LocalDateTime lastExecuteTime; + /** + * 当前通知次数 + */ + private Integer notifyTimes; + /** + * 最大可通知次数 + */ + private Integer maxNotifyTimes; + /** + * 通知地址 + */ + private String notifyUrl; + +} diff --git a/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/dal/dataobject/order/PayOrderDO.java b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/dal/dataobject/order/PayOrderDO.java new file mode 100644 index 0000000..ac0ef34 --- /dev/null +++ b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/dal/dataobject/order/PayOrderDO.java @@ -0,0 +1,138 @@ +package cn.iocoder.yudao.module.pay.dal.dataobject.order; + +import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; +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; +import lombok.*; + +import java.time.LocalDateTime; + +/** + * 支付订单 DO + * + * @author 芋道源码 + */ +@TableName("pay_order") +@KeySequence("pay_order_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class PayOrderDO extends BaseDO { + + /** + * 订单编号,数据库自增 + */ + private Long id; + /** + * 应用编号 + * + * 关联 {@link PayAppDO#getId()} + */ + private Long appId; + /** + * 渠道编号 + * + * 关联 {@link PayChannelDO#getId()} + */ + private Long channelId; + /** + * 渠道编码 + * + * 枚举 {@link PayChannelEnum} + */ + private String channelCode; + + // ========== 商户相关字段 ========== + + /** + * 商户订单编号 + * + * 例如说,内部系统 A 的订单号,需要保证每个 PayAppDO 唯一 + */ + private String merchantOrderId; + /** + * 商品标题 + */ + private String subject; + /** + * 商品描述信息 + */ + private String body; + /** + * 异步通知地址 + */ + private String notifyUrl; + + // ========== 订单相关字段 ========== + + /** + * 支付金额,单位:分 + */ + private Integer price; + /** + * 渠道手续费,单位:百分比 + * + * 冗余 {@link PayChannelDO#getFeeRate()} + */ + private Double channelFeeRate; + /** + * 渠道手续金额,单位:分 + */ + private Integer channelFeePrice; + /** + * 支付状态 + * + * 枚举 {@link PayOrderStatusEnum} + */ + private Integer status; + /** + * 用户 IP + */ + private String userIp; + /** + * 订单失效时间 + */ + private LocalDateTime expireTime; + /** + * 订单支付成功时间 + */ + private LocalDateTime successTime; + /** + * 支付成功的订单拓展单编号 + * + * 关联 {@link PayOrderExtensionDO#getId()} + */ + private Long extensionId; + /** + * 支付成功的外部订单号 + * + * 关联 {@link PayOrderExtensionDO#getNo()} + */ + private String no; + + // ========== 退款相关字段 ========== + /** + * 退款总金额,单位:分 + */ + private Integer refundPrice; + + // ========== 渠道相关字段 ========== + /** + * 渠道用户编号 + * + * 例如说,微信 openid、支付宝账号 + */ + private String channelUserId; + /** + * 渠道订单号 + */ + private String channelOrderNo; + +} diff --git a/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/dal/dataobject/order/PayOrderExtensionDO.java b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/dal/dataobject/order/PayOrderExtensionDO.java new file mode 100644 index 0000000..9d2779b --- /dev/null +++ b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/dal/dataobject/order/PayOrderExtensionDO.java @@ -0,0 +1,96 @@ +package cn.iocoder.yudao.module.pay.dal.dataobject.order; + +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; +import com.baomidou.mybatisplus.annotation.KeySequence; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler; +import lombok.*; + +import java.util.Map; + +/** + * 支付订单拓展 DO + * + * 每次调用支付渠道,都会生成一条对应记录 + * + * @author 芋道源码 + */ +@TableName(value = "pay_order_extension",autoResultMap = true) +@KeySequence("pay_order_extension_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class PayOrderExtensionDO extends BaseDO { + + /** + * 订单拓展编号,数据库自增 + */ + private Long id; + /** + * 外部订单号,根据规则生成 + * + * 调用支付渠道时,使用该字段作为对接的订单号: + * 1. 微信支付:对应 JSAPI 支付 的 out_trade_no 字段 + * 2. 支付宝支付:对应 电脑网站支付 的 out_trade_no 字段 + * + * 例如说,P202110132239124200055 + */ + private String no; + /** + * 订单号 + * + * 关联 {@link PayOrderDO#getId()} + */ + private Long orderId; + /** + * 渠道编号 + * + * 关联 {@link PayChannelDO#getId()} + */ + private Long channelId; + /** + * 渠道编码 + */ + private String channelCode; + /** + * 用户 IP + */ + private String userIp; + /** + * 支付状态 + * + * 枚举 {@link PayOrderStatusEnum} + */ + private Integer status; + /** + * 支付渠道的额外参数 + * + * 参见 参数说明 + */ + @TableField(typeHandler = JacksonTypeHandler.class) + private Map channelExtras; + + /** + * 调用渠道的错误码 + */ + private String channelErrorCode; + /** + * 调用渠道报错时,错误信息 + */ + private String channelErrorMsg; + + /** + * 支付渠道的同步/异步通知的内容 + * + * 对应 {@link PayOrderRespDTO#getRawData()} + */ + private String channelNotifyData; + +} diff --git a/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/dal/dataobject/refund/PayRefundDO.java b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/dal/dataobject/refund/PayRefundDO.java new file mode 100644 index 0000000..607483f --- /dev/null +++ b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/dal/dataobject/refund/PayRefundDO.java @@ -0,0 +1,160 @@ +package cn.iocoder.yudao.module.pay.dal.dataobject.refund; + +import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; +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; +import cn.iocoder.yudao.module.pay.enums.refund.PayRefundStatusEnum; +import com.baomidou.mybatisplus.annotation.KeySequence; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.*; + +import java.time.LocalDateTime; + +/** + * 支付退款单 DO + * 一个支付订单,可以拥有多个支付退款单 + * + * 即 PayOrderDO : PayRefundDO = 1 : n + * + * @author 芋道源码 + */ +@TableName("pay_refund") +@KeySequence("pay_refund_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class PayRefundDO extends BaseDO { + + /** + * 退款单编号,数据库自增 + */ + @TableId + private Long id; + /** + * 外部退款号,根据规则生成 + * + * 调用支付渠道时,使用该字段作为对接的退款号: + * 1. 微信退款:对应 申请退款 的 out_refund_no 字段 + * 2. 支付宝退款:对应 的 out_request_no 字段 + */ + private String no; + + /** + * 应用编号 + * + * 关联 {@link PayAppDO#getId()} + */ + private Long appId; + /** + * 渠道编号 + * + * 关联 {@link PayChannelDO#getId()} + */ + private Long channelId; + /** + * 渠道编码 + * + * 枚举 {@link PayChannelEnum} + */ + private String channelCode; + /** + * 订单编号 + * + * 关联 {@link PayOrderDO#getId()} + */ + private Long orderId; + /** + * 支付订单编号 + * + * 冗余 {@link PayOrderDO#getNo()} + */ + private String orderNo; + + // ========== 商户相关字段 ========== + /** + * 商户订单编号 + * + * 例如说,内部系统 A 的订单号,需要保证每个 PayAppDO 唯一 + */ + private String merchantOrderId; + /** + * 商户退款订单号 + * + * 例如说,内部系统 A 的订单号,需要保证每个 PayAppDO 唯一 + */ + private String merchantRefundId; + /** + * 异步通知地址 + */ + private String notifyUrl; + + // ========== 退款相关字段 ========== + /** + * 退款状态 + * + * 枚举 {@link PayRefundStatusEnum} + */ + private Integer status; + + /** + * 支付金额,单位:分 + */ + private Integer payPrice; + /** + * 退款金额,单位:分 + */ + private Integer refundPrice; + + /** + * 退款原因 + */ + private String reason; + + /** + * 用户 IP + */ + private String userIp; + + // ========== 渠道相关字段 ========== + /** + * 渠道订单号 + * + * 冗余 {@link PayOrderDO#getChannelOrderNo()} + */ + private String channelOrderNo; + /** + * 渠道退款单号 + * + * 1. 微信退款:对应 申请退款 的 refund_id 字段 + * 2. 支付宝退款:没有字段 + */ + private String channelRefundNo; + /** + * 退款成功时间 + */ + private LocalDateTime successTime; + + /** + * 调用渠道的错误码 + */ + private String channelErrorCode; + /** + * 调用渠道的错误提示 + */ + private String channelErrorMsg; + + /** + * 支付渠道的同步/异步通知的内容 + * + * 对应 {@link PayRefundRespDTO#getRawData()} + */ + private String channelNotifyData; + +} diff --git a/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/dal/dataobject/transfer/PayTransferDO.java b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/dal/dataobject/transfer/PayTransferDO.java new file mode 100644 index 0000000..94f8521 --- /dev/null +++ b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/dal/dataobject/transfer/PayTransferDO.java @@ -0,0 +1,146 @@ +package cn.iocoder.yudao.module.pay.dal.dataobject.transfer; + +import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; +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; +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler; +import lombok.Data; + +import java.time.LocalDateTime; +import java.util.Map; + +/** + * 转账单 DO + * + * @author jason + */ +@TableName(value ="pay_transfer", autoResultMap = true) +@KeySequence("pay_transfer_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 +@Data +public class PayTransferDO extends BaseDO { + + /** + * 编号 + */ + @TableId + private Long id; + + /** + * 转账单号 + */ + private String no; + + /** + * 应用编号 + * + * 关联 {@link PayAppDO#getId()} + */ + private Long appId; + + /** + * 转账渠道编号 + * + * 关联 {@link PayChannelDO#getId()} + */ + private Long channelId; + + /** + * 转账渠道编码 + * + * 枚举 {@link PayChannelEnum} + */ + private String channelCode; + + // ========== 商户相关字段 ========== + /** + * 商户转账单编号 + * + * 例如说,内部系统 A 的订单号,需要保证每个 PayAppDO 唯一 + */ + private String merchantTransferId; + + // ========== 转账相关字段 ========== + + /** + * 转账标题 + */ + private String subject; + + /** + * 转账金额,单位:分 + */ + private Integer price; + + /** + * 收款人账号 + */ + private String userAccount; + /** + * 收款人姓名 + */ + private String userName; + + /** + * 转账状态 + * + * 枚举 {@link PayTransferStatusEnum} + */ + private Integer status; + + /** + * 订单转账成功时间 + */ + private LocalDateTime successTime; + + // ========== 其它字段 ========== + + /** + * 异步通知地址 + */ + private String notifyUrl; + + /** + * 用户 IP + */ + private String userIp; + + /** + * 渠道的额外参数 + */ + @TableField(typeHandler = JacksonTypeHandler.class) + private Map channelExtras; + + /** + * 渠道转账单号 + */ + private String channelTransferNo; + + /** + * 调用渠道的错误码 + */ + private String channelErrorCode; + /** + * 调用渠道的错误提示 + */ + private String channelErrorMsg; + + /** + * 渠道的同步/异步通知的内容 + */ + private String channelNotifyData; + + /** + * 渠道 package 信息 + * + * 特殊:目前只有微信转账有这个东西!!! + * @see JSAPI 调起用户确认收款 + */ + private String channelPackageInfo; + +} \ No newline at end of file diff --git a/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/dal/dataobject/wallet/PayWalletDO.java b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/dal/dataobject/wallet/PayWalletDO.java new file mode 100644 index 0000000..a3c54c9 --- /dev/null +++ b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/dal/dataobject/wallet/PayWalletDO.java @@ -0,0 +1,59 @@ +package cn.iocoder.yudao.module.pay.dal.dataobject.wallet; + +import cn.iocoder.yudao.framework.common.enums.UserTypeEnum; +import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; +import com.baomidou.mybatisplus.annotation.KeySequence; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; + +/** + * 会员钱包 DO + * + * @author jason + */ +@TableName(value ="pay_wallet") +@KeySequence("pay_wallet_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 +@Data +public class PayWalletDO extends BaseDO { + + /** + * 编号 + */ + @TableId + private Long id; + + /** + * 用户 id + * + * 关联 MemberUserDO 的 id 编号 + * 关联 AdminUserDO 的 id 编号 + */ + private Long userId; + /** + * 用户类型, 预留 多商户转帐可能需要用到 + * + * 关联 {@link UserTypeEnum} + */ + private Integer userType; + + /** + * 余额,单位分 + */ + private Integer balance; + + /** + * 冻结金额,单位分 + */ + private Integer freezePrice; + + /** + * 累计支出,单位分 + */ + private Integer totalExpense; + /** + * 累计充值,单位分 + */ + private Integer totalRecharge; + +} diff --git a/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/dal/dataobject/wallet/PayWalletRechargeDO.java b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/dal/dataobject/wallet/PayWalletRechargeDO.java new file mode 100644 index 0000000..a842c95 --- /dev/null +++ b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/dal/dataobject/wallet/PayWalletRechargeDO.java @@ -0,0 +1,116 @@ +package cn.iocoder.yudao.module.pay.dal.dataobject.wallet; + +import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; +import cn.iocoder.yudao.module.pay.dal.dataobject.order.PayOrderDO; +import cn.iocoder.yudao.module.pay.dal.dataobject.refund.PayRefundDO; +import cn.iocoder.yudao.module.pay.enums.refund.PayRefundStatusEnum; +import com.baomidou.mybatisplus.annotation.KeySequence; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * 会员钱包充值 + */ +@TableName(value ="pay_wallet_recharge") +@KeySequence("pay_wallet_recharge_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 +@Data +public class PayWalletRechargeDO extends BaseDO { + + /** + * 编号 + */ + @TableId + private Long id; + + /** + * 钱包编号 + * + * 关联 {@link PayWalletDO#getId()} + */ + private Long walletId; + + /** + * 用户实际到账余额 + * + * 例如充 100 送 20,则该值是 120 + */ + private Integer totalPrice; + /** + * 实际支付金额 + */ + private Integer payPrice; + /** + * 钱包赠送金额 + */ + private Integer bonusPrice; + + /** + * 充值套餐编号 + * + * 关联 {@link PayWalletRechargeDO#getPackageId()} 字段 + */ + private Long packageId; + + /** + * 是否已支付 + * + * true - 已支付 + * false - 未支付 + */ + private Boolean payStatus; + + /** + * 支付订单编号 + * + * 关联 {@link PayOrderDO#getId()} + */ + private Long payOrderId; + + /** + * 支付成功的支付渠道 + * + * 冗余 {@link PayOrderDO#getChannelCode()} + */ + private String payChannelCode; + /** + * 订单支付时间 + */ + private LocalDateTime payTime; + + /** + * 支付退款单编号 + * + * 关联 {@link PayRefundDO#getId()} + */ + private Long payRefundId; + + /** + * 退款金额,包含赠送金额 + */ + private Integer refundTotalPrice; + /** + * 退款支付金额 + */ + private Integer refundPayPrice; + + /** + * 退款钱包赠送金额 + */ + private Integer refundBonusPrice; + + /** + * 退款时间 + */ + private LocalDateTime refundTime; + + /** + * 退款状态 + * + * 枚举 {@link PayRefundStatusEnum} + */ + private Integer refundStatus; + +} diff --git a/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/dal/dataobject/wallet/PayWalletRechargePackageDO.java b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/dal/dataobject/wallet/PayWalletRechargePackageDO.java new file mode 100644 index 0000000..72fc43d --- /dev/null +++ b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/dal/dataobject/wallet/PayWalletRechargePackageDO.java @@ -0,0 +1,47 @@ +package cn.iocoder.yudao.module.pay.dal.dataobject.wallet; + +import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; +import com.baomidou.mybatisplus.annotation.KeySequence; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; + +/** + * 会员钱包充值套餐 DO + * + * 通过充值套餐时,可以赠送一定金额; + * + * @author 芋道源码 + */ +@TableName(value ="pay_wallet_recharge_package") +@KeySequence("pay_wallet_recharge_package_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 +@Data +public class PayWalletRechargePackageDO extends BaseDO { + + /** + * 编号 + */ + @TableId + private Long id; + /** + * 套餐名 + */ + private String name; + + /** + * 支付金额 + */ + private Integer payPrice; + /** + * 赠送金额 + */ + private Integer bonusPrice; + + /** + * 状态 + * + * 枚举 {@link cn.iocoder.yudao.framework.common.enums.CommonStatusEnum} + */ + private Integer status; + +} diff --git a/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/dal/dataobject/wallet/PayWalletTransactionDO.java b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/dal/dataobject/wallet/PayWalletTransactionDO.java new file mode 100644 index 0000000..654e511 --- /dev/null +++ b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/dal/dataobject/wallet/PayWalletTransactionDO.java @@ -0,0 +1,66 @@ +package cn.iocoder.yudao.module.pay.dal.dataobject.wallet; + +import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; +import cn.iocoder.yudao.module.pay.enums.wallet.PayWalletBizTypeEnum; +import com.baomidou.mybatisplus.annotation.KeySequence; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; + +/** + * 会员钱包流水 DO + * + * @author jason + */ +@TableName(value ="pay_wallet_transaction") +@KeySequence("pay_wallet_transaction_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 +@Data +public class PayWalletTransactionDO extends BaseDO { + + /** + * 编号 + */ + @TableId + private Long id; + + /** + * 流水号 + */ + private String no; + + /** + * 钱包编号 + * + * 关联 {@link PayWalletDO#getId()} + */ + private Long walletId; + + /** + * 关联业务分类 + * + * 枚举 {@link PayWalletBizTypeEnum#getType()} + */ + private Integer bizType; + + /** + * 关联业务编号 + */ + private String bizId; + + /** + * 流水说明 + */ + private String title; + + /** + * 交易金额,单位分 + * + * 正值表示余额增加,负值表示余额减少 + */ + private Integer price; + + /** + * 交易后余额,单位分 + */ + private Integer balance; +} diff --git a/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/app/PayAppMapper.java b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/app/PayAppMapper.java new file mode 100644 index 0000000..07e190a --- /dev/null +++ b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/app/PayAppMapper.java @@ -0,0 +1,26 @@ +package cn.iocoder.yudao.module.pay.dal.mysql.app; + +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; +import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; +import cn.iocoder.yudao.module.pay.controller.admin.app.vo.PayAppPageReqVO; +import cn.iocoder.yudao.module.pay.dal.dataobject.app.PayAppDO; +import org.apache.ibatis.annotations.Mapper; + +@Mapper +public interface PayAppMapper extends BaseMapperX { + + default PageResult selectPage(PayAppPageReqVO reqVO) { + return selectPage(reqVO, new LambdaQueryWrapperX() + .likeIfPresent(PayAppDO::getName, reqVO.getName()) + .likeIfPresent(PayAppDO::getAppKey, reqVO.getAppKey()) + .eqIfPresent(PayAppDO::getStatus, reqVO.getStatus()) + .betweenIfPresent(PayAppDO::getCreateTime, reqVO.getCreateTime()) + .orderByDesc(PayAppDO::getId)); + } + + default PayAppDO selectByAppKey(String appKey) { + return selectOne(PayAppDO::getAppKey, appKey); + } + +} diff --git a/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/channel/PayChannelMapper.java b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/channel/PayChannelMapper.java new file mode 100644 index 0000000..e698498 --- /dev/null +++ b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/channel/PayChannelMapper.java @@ -0,0 +1,31 @@ +package cn.iocoder.yudao.module.pay.dal.mysql.channel; + +import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; +import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; +import cn.iocoder.yudao.module.pay.dal.dataobject.channel.PayChannelDO; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Select; + +import java.time.LocalDateTime; +import java.util.Collection; +import java.util.List; + +@Mapper +public interface PayChannelMapper extends BaseMapperX { + + default PayChannelDO selectByAppIdAndCode(Long appId, String code) { + return selectOne(PayChannelDO::getAppId, appId, PayChannelDO::getCode, code); + } + + default List selectListByAppIds(Collection appIds){ + return selectList(PayChannelDO::getAppId, appIds); + } + + default List selectListByAppId(Long appId, Integer status) { + return selectList(new LambdaQueryWrapperX() + .eq(PayChannelDO::getAppId, appId) + .eq(PayChannelDO::getStatus, status)); + } + +} diff --git a/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/demo/PayDemoOrderMapper.java b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/demo/PayDemoOrderMapper.java new file mode 100644 index 0000000..0a92c6b --- /dev/null +++ b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/demo/PayDemoOrderMapper.java @@ -0,0 +1,28 @@ +package cn.iocoder.yudao.module.pay.dal.mysql.demo; + +import cn.iocoder.yudao.framework.common.pojo.PageParam; +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; +import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; +import cn.iocoder.yudao.module.pay.dal.dataobject.demo.PayDemoOrderDO; +import org.apache.ibatis.annotations.Mapper; + +/** + * 示例订单 Mapper + * + * @author 芋道源码 + */ +@Mapper +public interface PayDemoOrderMapper extends BaseMapperX { + + default PageResult selectPage(PageParam reqVO) { + return selectPage(reqVO, new LambdaQueryWrapperX() + .orderByDesc(PayDemoOrderDO::getId)); + } + + default int updateByIdAndPayed(Long id, boolean wherePayed, PayDemoOrderDO updateObj) { + return update(updateObj, new LambdaQueryWrapperX() + .eq(PayDemoOrderDO::getId, id).eq(PayDemoOrderDO::getPayStatus, wherePayed)); + } + +} diff --git a/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/demo/PayDemoWithdrawMapper.java b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/demo/PayDemoWithdrawMapper.java new file mode 100644 index 0000000..393c161 --- /dev/null +++ b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/demo/PayDemoWithdrawMapper.java @@ -0,0 +1,24 @@ +package cn.iocoder.yudao.module.pay.dal.mysql.demo; + +import cn.iocoder.yudao.framework.common.pojo.PageParam; +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; +import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; +import cn.iocoder.yudao.module.pay.dal.dataobject.demo.PayDemoWithdrawDO; +import org.apache.ibatis.annotations.Mapper; + +@Mapper +public interface PayDemoWithdrawMapper extends BaseMapperX { + + default PageResult selectPage(PageParam pageParam){ + return selectPage(pageParam, new LambdaQueryWrapperX() + .orderByDesc(PayDemoWithdrawDO::getId)); + } + + default int updateByIdAndStatus(Long id, Integer whereStatus, PayDemoWithdrawDO updateObj) { + return update(updateObj, new LambdaQueryWrapperX() + .eq(PayDemoWithdrawDO::getId, id) + .eq(PayDemoWithdrawDO::getStatus, whereStatus)); + } + +} \ No newline at end of file diff --git a/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/notify/PayNotifyLogMapper.java b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/notify/PayNotifyLogMapper.java new file mode 100644 index 0000000..8b586df --- /dev/null +++ b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/notify/PayNotifyLogMapper.java @@ -0,0 +1,16 @@ +package cn.iocoder.yudao.module.pay.dal.mysql.notify; + +import cn.iocoder.yudao.module.pay.dal.dataobject.notify.PayNotifyLogDO; +import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; +import org.apache.ibatis.annotations.Mapper; + +import java.util.List; + +@Mapper +public interface PayNotifyLogMapper extends BaseMapperX { + + default List selectListByTaskId(Long taskId) { + return selectList(PayNotifyLogDO::getTaskId, taskId); + } + +} diff --git a/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/notify/PayNotifyTaskMapper.java b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/notify/PayNotifyTaskMapper.java new file mode 100644 index 0000000..a3e3eff --- /dev/null +++ b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/notify/PayNotifyTaskMapper.java @@ -0,0 +1,46 @@ +package cn.iocoder.yudao.module.pay.dal.mysql.notify; + +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; +import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; +import cn.iocoder.yudao.module.pay.controller.admin.notify.vo.PayNotifyTaskPageReqVO; +import cn.iocoder.yudao.module.pay.dal.dataobject.notify.PayNotifyTaskDO; +import cn.iocoder.yudao.module.pay.enums.notify.PayNotifyStatusEnum; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import org.apache.ibatis.annotations.Mapper; + +import java.time.LocalDateTime; +import java.util.List; + +@Mapper +public interface PayNotifyTaskMapper extends BaseMapperX { + + /** + * 获得需要通知的 PayNotifyTaskDO 记录。需要满足如下条件: + * + * 1. status 非成功 + * 2. nextNotifyTime 小于当前时间 + * + * @return PayTransactionNotifyTaskDO 数组 + */ + default List selectListByNotify() { + return selectList(new LambdaQueryWrapper() + .in(PayNotifyTaskDO::getStatus, PayNotifyStatusEnum.WAITING.getStatus(), + PayNotifyStatusEnum.REQUEST_SUCCESS.getStatus(), PayNotifyStatusEnum.REQUEST_FAILURE.getStatus()) + .le(PayNotifyTaskDO::getNextNotifyTime, LocalDateTime.now())); + } + + default PageResult selectPage(PayNotifyTaskPageReqVO reqVO) { + return selectPage(reqVO, new LambdaQueryWrapperX() + .eqIfPresent(PayNotifyTaskDO::getAppId, reqVO.getAppId()) + .eqIfPresent(PayNotifyTaskDO::getType, reqVO.getType()) + .eqIfPresent(PayNotifyTaskDO::getDataId, reqVO.getDataId()) + .eqIfPresent(PayNotifyTaskDO::getStatus, reqVO.getStatus()) + .eqIfPresent(PayNotifyTaskDO::getMerchantOrderId, reqVO.getMerchantOrderId()) + .eqIfPresent(PayNotifyTaskDO::getMerchantRefundId, reqVO.getMerchantRefundId()) + .eqIfPresent(PayNotifyTaskDO::getMerchantTransferId, reqVO.getMerchantTransferId()) + .betweenIfPresent(PayNotifyTaskDO::getCreateTime, reqVO.getCreateTime()) + .orderByDesc(PayNotifyTaskDO::getId)); + } + +} diff --git a/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/order/PayOrderExtensionMapper.java b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/order/PayOrderExtensionMapper.java new file mode 100755 index 0000000..eebcd0e --- /dev/null +++ b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/order/PayOrderExtensionMapper.java @@ -0,0 +1,38 @@ +package cn.iocoder.yudao.module.pay.dal.mysql.order; + +import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; +import cn.iocoder.yudao.module.pay.dal.dataobject.order.PayOrderExtensionDO; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import org.apache.ibatis.annotations.Mapper; + +import java.time.LocalDateTime; +import java.util.List; + +@Mapper +public interface PayOrderExtensionMapper extends BaseMapperX { + + default PayOrderExtensionDO selectByNo(String no) { + return selectOne(PayOrderExtensionDO::getNo, no); + } + + default int updateByIdAndStatus(Long id, Integer status, PayOrderExtensionDO update) { + return update(update, new LambdaQueryWrapper() + .eq(PayOrderExtensionDO::getId, id).eq(PayOrderExtensionDO::getStatus, status)); + } + + default List selectListByOrderId(Long orderId) { + return selectList(PayOrderExtensionDO::getOrderId, orderId); + } + + default List selectListByOrderIdAndStatus(Long orderId, Integer status) { + return selectList(PayOrderExtensionDO::getOrderId, orderId, + PayOrderExtensionDO::getStatus, status); + } + + default List selectListByStatusAndCreateTimeGe(Integer status, LocalDateTime minCreateTime) { + return selectList(new LambdaQueryWrapper() + .eq(PayOrderExtensionDO::getStatus, status) + .ge(PayOrderExtensionDO::getCreateTime, minCreateTime)); + } + +} diff --git a/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/order/PayOrderMapper.java b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/order/PayOrderMapper.java new file mode 100755 index 0000000..95510d5 --- /dev/null +++ b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/order/PayOrderMapper.java @@ -0,0 +1,62 @@ +package cn.iocoder.yudao.module.pay.dal.mysql.order; + +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; +import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; +import cn.iocoder.yudao.module.pay.controller.admin.order.vo.PayOrderExportReqVO; +import cn.iocoder.yudao.module.pay.controller.admin.order.vo.PayOrderPageReqVO; +import cn.iocoder.yudao.module.pay.dal.dataobject.order.PayOrderDO; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import org.apache.ibatis.annotations.Mapper; + +import java.time.LocalDateTime; +import java.util.List; + +@Mapper +public interface PayOrderMapper extends BaseMapperX { + + default PageResult selectPage(PayOrderPageReqVO reqVO) { + return selectPage(reqVO, new LambdaQueryWrapperX() + .eqIfPresent(PayOrderDO::getAppId, reqVO.getAppId()) + .eqIfPresent(PayOrderDO::getChannelCode, reqVO.getChannelCode()) + .likeIfPresent(PayOrderDO::getMerchantOrderId, reqVO.getMerchantOrderId()) + .likeIfPresent(PayOrderDO::getChannelOrderNo, reqVO.getChannelOrderNo()) + .likeIfPresent(PayOrderDO::getNo, reqVO.getNo()) + .eqIfPresent(PayOrderDO::getStatus, reqVO.getStatus()) + .betweenIfPresent(PayOrderDO::getCreateTime, reqVO.getCreateTime()) + .orderByDesc(PayOrderDO::getId)); + } + + default List selectList(PayOrderExportReqVO reqVO) { + return selectList(new LambdaQueryWrapperX() + .eqIfPresent(PayOrderDO::getAppId, reqVO.getAppId()) + .eqIfPresent(PayOrderDO::getChannelCode, reqVO.getChannelCode()) + .likeIfPresent(PayOrderDO::getMerchantOrderId, reqVO.getMerchantOrderId()) + .likeIfPresent(PayOrderDO::getChannelOrderNo, reqVO.getChannelOrderNo()) + .likeIfPresent(PayOrderDO::getNo, reqVO.getNo()) + .eqIfPresent(PayOrderDO::getStatus, reqVO.getStatus()) + .betweenIfPresent(PayOrderDO::getCreateTime, reqVO.getCreateTime()) + .orderByDesc(PayOrderDO::getId)); + } + + default Long selectCountByAppId(Long appId) { + return selectCount(PayOrderDO::getAppId, appId); + } + + default PayOrderDO selectByAppIdAndMerchantOrderId(Long appId, String merchantOrderId) { + return selectOne(PayOrderDO::getAppId, appId, + PayOrderDO::getMerchantOrderId, merchantOrderId); + } + + default int updateByIdAndStatus(Long id, Integer status, PayOrderDO update) { + return update(update, new LambdaQueryWrapper() + .eq(PayOrderDO::getId, id).eq(PayOrderDO::getStatus, status)); + } + + default List selectListByStatusAndExpireTimeLt(Integer status, LocalDateTime expireTime) { + return selectList(new LambdaQueryWrapper() + .eq(PayOrderDO::getStatus, status) + .lt(PayOrderDO::getExpireTime, expireTime)); + } + +} diff --git a/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/refund/PayRefundMapper.java b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/refund/PayRefundMapper.java new file mode 100755 index 0000000..0b620ea --- /dev/null +++ b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/refund/PayRefundMapper.java @@ -0,0 +1,78 @@ +package cn.iocoder.yudao.module.pay.dal.mysql.refund; + +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; +import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; +import cn.iocoder.yudao.module.pay.controller.admin.refund.vo.PayRefundExportReqVO; +import cn.iocoder.yudao.module.pay.controller.admin.refund.vo.PayRefundPageReqVO; +import cn.iocoder.yudao.module.pay.dal.dataobject.refund.PayRefundDO; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import org.apache.ibatis.annotations.Mapper; + +import java.util.List; + +@Mapper +public interface PayRefundMapper extends BaseMapperX { + + default Long selectCountByAppId(Long appId) { + return selectCount(PayRefundDO::getAppId, appId); + } + + default PayRefundDO selectByAppIdAndMerchantRefundId(Long appId, String merchantRefundId) { + return selectOne(new LambdaQueryWrapperX() + .eq(PayRefundDO::getAppId, appId) + .eq(PayRefundDO::getMerchantRefundId, merchantRefundId)); + } + + default Long selectCountByAppIdAndOrderId(Long appId, Long orderId, Integer status) { + return selectCount(new LambdaQueryWrapperX() + .eq(PayRefundDO::getAppId, appId) + .eq(PayRefundDO::getOrderId, orderId) + .eq(PayRefundDO::getStatus, status)); + } + + default PayRefundDO selectByAppIdAndNo(Long appId, String no) { + return selectOne(new LambdaQueryWrapperX() + .eq(PayRefundDO::getAppId, appId) + .eq(PayRefundDO::getNo, no)); + } + + default PayRefundDO selectByNo(String no) { + return selectOne(PayRefundDO::getNo, no); + } + + default int updateByIdAndStatus(Long id, Integer status, PayRefundDO update) { + return update(update, new LambdaQueryWrapper() + .eq(PayRefundDO::getId, id).eq(PayRefundDO::getStatus, status)); + } + + default PageResult selectPage(PayRefundPageReqVO reqVO) { + return selectPage(reqVO, new LambdaQueryWrapperX() + .eqIfPresent(PayRefundDO::getAppId, reqVO.getAppId()) + .eqIfPresent(PayRefundDO::getChannelCode, reqVO.getChannelCode()) + .likeIfPresent(PayRefundDO::getMerchantOrderId, reqVO.getMerchantOrderId()) + .likeIfPresent(PayRefundDO::getMerchantRefundId, reqVO.getMerchantRefundId()) + .likeIfPresent(PayRefundDO::getChannelOrderNo, reqVO.getChannelOrderNo()) + .likeIfPresent(PayRefundDO::getChannelRefundNo, reqVO.getChannelRefundNo()) + .eqIfPresent(PayRefundDO::getStatus, reqVO.getStatus()) + .betweenIfPresent(PayRefundDO::getCreateTime, reqVO.getCreateTime()) + .orderByDesc(PayRefundDO::getId)); + } + + default List selectList(PayRefundExportReqVO reqVO) { + return selectList(new LambdaQueryWrapperX() + .eqIfPresent(PayRefundDO::getAppId, reqVO.getAppId()) + .eqIfPresent(PayRefundDO::getChannelCode, reqVO.getChannelCode()) + .likeIfPresent(PayRefundDO::getMerchantOrderId, reqVO.getMerchantOrderId()) + .likeIfPresent(PayRefundDO::getMerchantRefundId, reqVO.getMerchantRefundId()) + .likeIfPresent(PayRefundDO::getChannelOrderNo, reqVO.getChannelOrderNo()) + .likeIfPresent(PayRefundDO::getChannelRefundNo, reqVO.getChannelRefundNo()) + .eqIfPresent(PayRefundDO::getStatus, reqVO.getStatus()) + .betweenIfPresent(PayRefundDO::getCreateTime, reqVO.getCreateTime()) + .orderByDesc(PayRefundDO::getId)); + } + + default List selectListByStatus(Integer status) { + return selectList(PayRefundDO::getStatus, status); + } +} diff --git a/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/transfer/PayTransferMapper.java b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/transfer/PayTransferMapper.java new file mode 100644 index 0000000..59e6b3e --- /dev/null +++ b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/transfer/PayTransferMapper.java @@ -0,0 +1,65 @@ +package cn.iocoder.yudao.module.pay.dal.mysql.transfer; + +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; +import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; +import cn.iocoder.yudao.module.pay.controller.admin.transfer.vo.PayTransferPageReqVO; +import cn.iocoder.yudao.module.pay.dal.dataobject.transfer.PayTransferDO; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import org.apache.ibatis.annotations.Mapper; + +import java.util.Collection; +import java.util.List; + +@Mapper +public interface PayTransferMapper extends BaseMapperX { + + default int updateByIdAndStatus(Long id, List whereStatuses, PayTransferDO updateObj) { + return update(updateObj, new LambdaQueryWrapper() + .eq(PayTransferDO::getId, id) + .in(PayTransferDO::getStatus, whereStatuses)); + } + + default int updateByIdAndStatus(Long id, Integer whereStatus, PayTransferDO updateObj) { + return update(updateObj, new LambdaQueryWrapper() + .eq(PayTransferDO::getId, id) + .eq(PayTransferDO::getStatus, whereStatus)); + } + + default PayTransferDO selectByAppIdAndMerchantOrderId(Long appId, String merchantOrderId) { + return selectOne(PayTransferDO::getAppId, appId, + PayTransferDO::getMerchantTransferId, merchantOrderId); + } + + default PageResult selectPage(PayTransferPageReqVO reqVO) { + return selectPage(reqVO, new LambdaQueryWrapperX() + .eqIfPresent(PayTransferDO::getNo, reqVO.getNo()) + .eqIfPresent(PayTransferDO::getAppId, reqVO.getAppId()) + .eqIfPresent(PayTransferDO::getChannelCode, reqVO.getChannelCode()) + .eqIfPresent(PayTransferDO::getMerchantTransferId, reqVO.getMerchantOrderId()) + .eqIfPresent(PayTransferDO::getStatus, reqVO.getStatus()) + .likeIfPresent(PayTransferDO::getUserName, reqVO.getUserName()) + .likeIfPresent(PayTransferDO::getUserAccount, reqVO.getUserAccount()) + .eqIfPresent(PayTransferDO::getChannelTransferNo, reqVO.getChannelTransferNo()) + .betweenIfPresent(PayTransferDO::getCreateTime, reqVO.getCreateTime()) + .orderByDesc(PayTransferDO::getId)); + } + + default List selectListByStatus(Collection statuses) { + return selectList(PayTransferDO::getStatus, statuses); + } + + default PayTransferDO selectByAppIdAndNo(Long appId, String no) { + return selectOne(PayTransferDO::getAppId, appId, + PayTransferDO::getNo, no); + } + + default PayTransferDO selectByNo(String no) { + return selectOne(PayTransferDO::getNo, no); + } + +} + + + + diff --git a/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/wallet/PayWalletMapper.java b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/wallet/PayWalletMapper.java new file mode 100644 index 0000000..19a76cd --- /dev/null +++ b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/wallet/PayWalletMapper.java @@ -0,0 +1,134 @@ +package cn.iocoder.yudao.module.pay.dal.mysql.wallet; + + +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; +import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; +import cn.iocoder.yudao.module.pay.controller.admin.wallet.vo.wallet.PayWalletPageReqVO; +import cn.iocoder.yudao.module.pay.dal.dataobject.wallet.PayWalletDO; +import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; +import org.apache.ibatis.annotations.Mapper; + +@Mapper +public interface PayWalletMapper extends BaseMapperX { + + default PayWalletDO selectByUserIdAndType(Long userId, Integer userType) { + return selectOne(PayWalletDO::getUserId, userId, + PayWalletDO::getUserType, userType); + } + + default PageResult selectPage(PayWalletPageReqVO reqVO) { + return selectPage(reqVO, new LambdaQueryWrapperX() + .eqIfPresent(PayWalletDO::getUserId, reqVO.getUserId()) + .eqIfPresent(PayWalletDO::getUserType, reqVO.getUserType()) + .betweenIfPresent(PayWalletDO::getCreateTime, reqVO.getCreateTime()) + .orderByDesc(PayWalletDO::getId)); + } + + /** + * 当消费退款时候, 更新钱包 + * + * @param id 钱包 id + * @param price 消费金额 + */ + default int updateWhenConsumptionRefund(Long id, Integer price) { + LambdaUpdateWrapper lambdaUpdateWrapper = new LambdaUpdateWrapper() + .setSql(" balance = balance + " + price + + ", total_expense = total_expense - " + price) + .eq(PayWalletDO::getId, id); + return update(null, lambdaUpdateWrapper); + } + + /** + * 当消费时候, 更新钱包 + * + * @param price 消费金额 + * @param id 钱包 id + */ + default int updateWhenConsumption(Long id, Integer price){ + LambdaUpdateWrapper lambdaUpdateWrapper = new LambdaUpdateWrapper() + .setSql(" balance = balance - " + price + + ", total_expense = total_expense + " + price) + .eq(PayWalletDO::getId, id) + .ge(PayWalletDO::getBalance, price); // cas 逻辑 + return update(null, lambdaUpdateWrapper); + } + + /** + * 当充值的时候,更新钱包 + * + * @param id 钱包 id + * @param price 钱包金额 + */ + default int updateWhenRecharge(Long id, Integer price){ + LambdaUpdateWrapper lambdaUpdateWrapper = new LambdaUpdateWrapper() + .setSql(" balance = balance + " + price + + ", total_recharge = total_recharge + " + price) + .eq(PayWalletDO::getId, id); + return update(null, lambdaUpdateWrapper); + } + + /** + * 增加余额的时候,更新钱包 + * + * @param id 钱包 id + * @param price 钱包金额 + */ + default void updateWhenAdd(Long id, Integer price) { + LambdaUpdateWrapper lambdaUpdateWrapper = new LambdaUpdateWrapper() + .setSql(" balance = balance + " + price) + .eq(PayWalletDO::getId, id); + update(null, lambdaUpdateWrapper); + } + + /** + * 冻结钱包部分余额 + * + * @param id 钱包 id + * @param price 冻结金额 + */ + default int freezePrice(Long id, Integer price){ + LambdaUpdateWrapper lambdaUpdateWrapper = new LambdaUpdateWrapper() + .setSql(" balance = balance - " + price + + ", freeze_price = freeze_price + " + price) + .eq(PayWalletDO::getId, id) + .ge(PayWalletDO::getBalance, price); // cas 逻辑 + return update(null, lambdaUpdateWrapper); + } + + /** + * 解冻钱包余额 + * + * @param id 钱包 id + * @param price 解冻金额 + */ + default int unFreezePrice(Long id, Integer price){ + LambdaUpdateWrapper lambdaUpdateWrapper = new LambdaUpdateWrapper() + .setSql(" balance = balance + " + price + + ", freeze_price = freeze_price - " + price) + .eq(PayWalletDO::getId, id) + .ge(PayWalletDO::getFreezePrice, price); // cas 逻辑 + return update(null, lambdaUpdateWrapper); + } + + /** + * 当充值退款时, 更新钱包 + * + * @param id 钱包 id + * @param price 退款金额 + */ + default int updateWhenRechargeRefund(Long id, Integer price){ + LambdaUpdateWrapper lambdaUpdateWrapper = new LambdaUpdateWrapper() + .setSql(" freeze_price = freeze_price - " + price + + ", total_recharge = total_recharge - " + price) + .eq(PayWalletDO::getId, id) + .ge(PayWalletDO::getFreezePrice, price) + .ge(PayWalletDO::getTotalRecharge, price);// cas 逻辑 + return update(null, lambdaUpdateWrapper); + } + +} + + + + diff --git a/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/wallet/PayWalletRechargeMapper.java b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/wallet/PayWalletRechargeMapper.java new file mode 100644 index 0000000..f82b885 --- /dev/null +++ b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/wallet/PayWalletRechargeMapper.java @@ -0,0 +1,31 @@ +package cn.iocoder.yudao.module.pay.dal.mysql.wallet; + +import cn.iocoder.yudao.framework.common.pojo.PageParam; +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; +import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; +import cn.iocoder.yudao.module.pay.dal.dataobject.wallet.PayWalletRechargeDO; +import cn.iocoder.yudao.module.pay.dal.dataobject.wallet.PayWalletRechargePackageDO; +import org.apache.ibatis.annotations.Mapper; + +@Mapper +public interface PayWalletRechargeMapper extends BaseMapperX { + + default int updateByIdAndPaid(Long id, boolean wherePayStatus, PayWalletRechargeDO updateObj) { + return update(updateObj, new LambdaQueryWrapperX() + .eq(PayWalletRechargeDO::getId, id).eq(PayWalletRechargeDO::getPayStatus, wherePayStatus)); + } + + default int updateByIdAndRefunded(Long id, Integer whereRefundStatus, PayWalletRechargeDO updateObj) { + return update(updateObj, new LambdaQueryWrapperX() + .eq(PayWalletRechargeDO::getId, id).eq(PayWalletRechargeDO::getRefundStatus, whereRefundStatus)); + } + + default PageResult selectPage(PageParam pageReqVO, Long walletId, Boolean payStatus) { + return selectPage(pageReqVO, new LambdaQueryWrapperX() + .eq(PayWalletRechargeDO::getWalletId, walletId) + .eq(PayWalletRechargeDO::getPayStatus, payStatus) + .orderByDesc(PayWalletRechargeDO::getId)); + } + +} \ No newline at end of file diff --git a/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/wallet/PayWalletRechargePackageMapper.java b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/wallet/PayWalletRechargePackageMapper.java new file mode 100644 index 0000000..821908c --- /dev/null +++ b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/wallet/PayWalletRechargePackageMapper.java @@ -0,0 +1,32 @@ +package cn.iocoder.yudao.module.pay.dal.mysql.wallet; + + +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; +import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; +import cn.iocoder.yudao.module.pay.controller.admin.wallet.vo.rechargepackage.WalletRechargePackagePageReqVO; +import cn.iocoder.yudao.module.pay.dal.dataobject.wallet.PayWalletRechargePackageDO; +import org.apache.ibatis.annotations.Mapper; + +import java.util.List; + +@Mapper +public interface PayWalletRechargePackageMapper extends BaseMapperX { + + default PageResult selectPage(WalletRechargePackagePageReqVO reqVO) { + return selectPage(reqVO, new LambdaQueryWrapperX() + .likeIfPresent(PayWalletRechargePackageDO::getName, reqVO.getName()) + .eqIfPresent(PayWalletRechargePackageDO::getStatus, reqVO.getStatus()) + .betweenIfPresent(PayWalletRechargePackageDO::getCreateTime, reqVO.getCreateTime()) + .orderByDesc(PayWalletRechargePackageDO::getPayPrice)); + } + + default PayWalletRechargePackageDO selectByName(String name) { + return selectOne(PayWalletRechargePackageDO::getName, name); + } + + default List selectListByStatus(Integer status) { + return selectList(PayWalletRechargePackageDO::getStatus, status); + } + +} diff --git a/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/wallet/PayWalletTransactionMapper.java b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/wallet/PayWalletTransactionMapper.java new file mode 100644 index 0000000..212a90c --- /dev/null +++ b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/wallet/PayWalletTransactionMapper.java @@ -0,0 +1,67 @@ +package cn.iocoder.yudao.module.pay.dal.mysql.wallet; + + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.map.MapUtil; +import cn.iocoder.yudao.framework.common.pojo.PageParam; +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; +import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; +import cn.iocoder.yudao.framework.mybatis.core.query.QueryWrapperX; +import cn.iocoder.yudao.module.pay.controller.app.wallet.vo.transaction.AppPayWalletTransactionPageReqVO; +import cn.iocoder.yudao.module.pay.dal.dataobject.wallet.PayWalletTransactionDO; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import org.apache.ibatis.annotations.Mapper; + +import java.time.LocalDateTime; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.function.Consumer; + +import static cn.iocoder.yudao.module.pay.controller.app.wallet.vo.transaction.AppPayWalletTransactionPageReqVO.*; + +@Mapper +public interface PayWalletTransactionMapper extends BaseMapperX { + + default PageResult selectPage(Long walletId, Integer type, + PageParam pageParam, LocalDateTime[] createTime) { + LambdaQueryWrapperX query = new LambdaQueryWrapperX() + .eqIfPresent(PayWalletTransactionDO::getWalletId, walletId); + if (Objects.equals(type, TYPE_INCOME)) { + query.gt(PayWalletTransactionDO::getPrice, 0); + } else if (Objects.equals(type, TYPE_EXPENSE)) { + query.lt(PayWalletTransactionDO::getPrice, 0); + } + query.betweenIfPresent(PayWalletTransactionDO::getCreateTime, createTime); + query.orderByDesc(PayWalletTransactionDO::getId); + return selectPage(pageParam, query); + } + + default Integer selectPriceSum(Long walletId, Integer type, LocalDateTime[] createTime) { + // SQL sum 查询 + List> result = selectMaps(new QueryWrapperX() + .select("SUM(price) AS priceSum") + .gt(Objects.equals(type, TYPE_INCOME), "price", 0) // 收入 + .lt(Objects.equals(type, TYPE_EXPENSE), "price", 0) // 支出 + .eq("wallet_id", walletId) + .between("create_time", createTime[0], createTime[1])); + // 获得 sum 结果 + Map first = CollUtil.getFirst(result); + return MapUtil.getInt(first, "priceSum", 0); + } + + default PayWalletTransactionDO selectByNo(String no) { + return selectOne(PayWalletTransactionDO::getNo, no); + } + + default PayWalletTransactionDO selectByBiz(String bizId, Integer bizType) { + return selectOne(PayWalletTransactionDO::getBizId, bizId, + PayWalletTransactionDO::getBizType, bizType); + } + +} + + + + diff --git a/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/dal/redis/RedisKeyConstants.java b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/dal/redis/RedisKeyConstants.java new file mode 100644 index 0000000..6de0e21 --- /dev/null +++ b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/dal/redis/RedisKeyConstants.java @@ -0,0 +1,36 @@ +package cn.iocoder.yudao.module.pay.dal.redis; + +/** + * 支付 Redis Key 枚举类 + * + * @author 芋道源码 + */ +public interface RedisKeyConstants { + + /** + * 通知任务的分布式锁 + * + * KEY 格式:pay_notify:lock:%d // 参数来自 DefaultLockKeyBuilder 类 + * VALUE 数据格式:HASH // RLock.class:Redisson 的 Lock 锁,使用 Hash 数据结构 + * 过期时间:不固定 + */ + String PAY_NOTIFY_LOCK = "pay_notify:lock:%d"; + + /** + * 支付钱包的分布式锁 + * + * KEY 格式:pay_wallet:lock:%d + * VALUE 数据格式:HASH // RLock.class:Redisson 的 Lock 锁,使用 Hash 数据结构 + * 过期时间:不固定 + */ + String PAY_WALLET_LOCK = "pay_wallet:lock:%d"; + + /** + * 支付序号的缓存 + * + * KEY 格式:pay_no:{prefix} + * VALUE 数据格式:编号自增 + */ + String PAY_NO = "pay_no:"; + +} diff --git a/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/dal/redis/no/PayNoRedisDAO.java b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/dal/redis/no/PayNoRedisDAO.java new file mode 100644 index 0000000..1482ddf --- /dev/null +++ b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/dal/redis/no/PayNoRedisDAO.java @@ -0,0 +1,39 @@ +package cn.iocoder.yudao.module.pay.dal.redis.no; + +import cn.hutool.core.date.DatePattern;import cn.hutool.core.date.DateUtil; +import cn.iocoder.yudao.module.pay.dal.redis.RedisKeyConstants; +import org.springframework.data.redis.core.StringRedisTemplate; +import org.springframework.stereotype.Repository; + +import jakarta.annotation.Resource; +import java.time.Duration; +import java.time.LocalDateTime; + +/** + * 支付序号的 Redis DAO + * + * @author 芋道源码 + */ +@Repository +public class PayNoRedisDAO { + + @Resource + private StringRedisTemplate stringRedisTemplate; + + /** + * 生成序号 + * + * @param prefix 前缀 + * @return 序号 + */ + public String generate(String prefix) { + // 递增序号 + String noPrefix = prefix + DateUtil.format(LocalDateTime.now(), DatePattern.PURE_DATETIME_PATTERN); + String key = RedisKeyConstants.PAY_NO + noPrefix; + Long no = stringRedisTemplate.opsForValue().increment(key); + // 设置过期时间 + stringRedisTemplate.expire(key, Duration.ofMinutes(1L)); + return noPrefix + no; + } + +} diff --git a/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/dal/redis/notify/PayNotifyLockRedisDAO.java b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/dal/redis/notify/PayNotifyLockRedisDAO.java new file mode 100644 index 0000000..b5916f6 --- /dev/null +++ b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/dal/redis/notify/PayNotifyLockRedisDAO.java @@ -0,0 +1,39 @@ +package cn.iocoder.yudao.module.pay.dal.redis.notify; + +import org.redisson.api.RLock; +import org.redisson.api.RedissonClient; +import org.springframework.stereotype.Repository; + +import jakarta.annotation.Resource; +import java.util.concurrent.TimeUnit; + +import static cn.iocoder.yudao.module.pay.dal.redis.RedisKeyConstants.PAY_NOTIFY_LOCK; + +/** + * 支付通知的锁 Redis DAO + * + * @author 芋道源码 + */ +@Repository +public class PayNotifyLockRedisDAO { + + @Resource + private RedissonClient redissonClient; + + public void lock(Long id, Long timeoutMillis, Runnable runnable) { + String lockKey = formatKey(id); + RLock lock = redissonClient.getLock(lockKey); + try { + lock.lock(timeoutMillis, TimeUnit.MILLISECONDS); + // 执行逻辑 + runnable.run(); + } finally { + lock.unlock(); + } + } + + private static String formatKey(Long id) { + return String.format(PAY_NOTIFY_LOCK, id); + } + +} diff --git a/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/dal/redis/wallet/PayWalletLockRedisDAO.java b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/dal/redis/wallet/PayWalletLockRedisDAO.java new file mode 100644 index 0000000..1c42727 --- /dev/null +++ b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/dal/redis/wallet/PayWalletLockRedisDAO.java @@ -0,0 +1,42 @@ +package cn.iocoder.yudao.module.pay.dal.redis.wallet; + +import jakarta.annotation.Resource; +import org.redisson.api.RLock; +import org.redisson.api.RedissonClient; +import org.springframework.stereotype.Repository; + +import java.util.concurrent.Callable; +import java.util.concurrent.TimeUnit; + +import static cn.iocoder.yudao.module.pay.dal.redis.RedisKeyConstants.PAY_WALLET_LOCK; + +/** + * 支付钱包的锁 Redis DAO + * + * @author 芋道源码 + */ +@Repository +public class PayWalletLockRedisDAO { + + @Resource + private RedissonClient redissonClient; + + public V lock(Long id, Long timeoutMillis, Callable callable) throws Exception { + String lockKey = formatKey(id); + RLock lock = redissonClient.getLock(lockKey); + try { + lock.lock(timeoutMillis, TimeUnit.MILLISECONDS); + // 执行逻辑 + return callable.call(); + } catch (Exception e) { + throw e; + } finally { + lock.unlock(); + } + } + + private static String formatKey(Long id) { + return String.format(PAY_WALLET_LOCK, id); + } + +} diff --git a/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/enums/DictTypeConstants.java b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/enums/DictTypeConstants.java new file mode 100644 index 0000000..680ed9b --- /dev/null +++ b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/enums/DictTypeConstants.java @@ -0,0 +1,20 @@ +package cn.iocoder.yudao.module.pay.enums; + +/** + * Pay 字典类型的枚举类 + * + * @author 芋道源码 + */ +public interface DictTypeConstants { + + String CHANNEL_CODE = "pay_channel_code"; // 支付渠道编码 + + String ORDER_STATUS = "pay_order_status"; // 支付渠道 + + String REFUND_STATUS = "pay_order_status"; // 退款状态 + + String NOTIFY_STATUS = "pay_notify_status"; // 回调状态 + + String TRANSFER_STATUS = "pay_transfer_status"; // 转账状态 + +} diff --git a/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/enums/ErrorCodeConstants.java b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/enums/ErrorCodeConstants.java new file mode 100644 index 0000000..660cc23 --- /dev/null +++ b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/enums/ErrorCodeConstants.java @@ -0,0 +1,97 @@ +package cn.iocoder.yudao.module.pay.enums; + +import cn.iocoder.yudao.framework.common.exception.ErrorCode; + +/** + * Pay 错误码 Core 枚举类 + * + * pay 系统,使用 1-007-000-000 段 + */ +public interface ErrorCodeConstants { + + // ========== APP 模块 1-007-000-000 ========== + ErrorCode APP_NOT_FOUND = new ErrorCode(1_007_000_000, "App 不存在"); + ErrorCode APP_IS_DISABLE = new ErrorCode(1_007_000_002, "App 已经被禁用"); + ErrorCode APP_EXIST_ORDER_CANT_DELETE = new ErrorCode(1_007_000_003, "支付应用存在支付订单,无法删除"); + ErrorCode APP_EXIST_REFUND_CANT_DELETE = new ErrorCode(1_007_000_004, "支付应用存在退款订单,无法删除"); + ErrorCode APP_KEY_EXISTS = new ErrorCode(1_007_000_005, "支付应用标识已经存在"); + + // ========== CHANNEL 模块 1-007-001-000 ========== + ErrorCode CHANNEL_NOT_FOUND = new ErrorCode(1_007_001_000, "支付渠道的配置不存在"); + ErrorCode CHANNEL_IS_DISABLE = new ErrorCode(1_007_001_001, "支付渠道已经禁用"); + ErrorCode CHANNEL_EXIST_SAME_CHANNEL_ERROR = new ErrorCode(1_007_001_004, "已存在相同的渠道"); + + // ========== ORDER 模块 1-007-002-000 ========== + ErrorCode PAY_ORDER_NOT_FOUND = new ErrorCode(1_007_002_000, "支付订单不存在"); + ErrorCode PAY_ORDER_STATUS_IS_NOT_WAITING = new ErrorCode(1_007_002_001, "支付订单不处于待支付"); + ErrorCode PAY_ORDER_STATUS_IS_SUCCESS = new ErrorCode(1_007_002_002, "订单已支付,请刷新页面"); + ErrorCode PAY_ORDER_IS_EXPIRED = new ErrorCode(1_007_002_003, "支付订单已经过期"); + ErrorCode PAY_ORDER_SUBMIT_CHANNEL_ERROR = new ErrorCode(1_007_002_004, "发起支付报错,错误码:{},错误提示:{}"); + ErrorCode PAY_ORDER_REFUND_FAIL_STATUS_ERROR = new ErrorCode(1_007_002_005, "支付订单退款失败,原因:状态不是已支付或已退款"); + + // ========== ORDER 模块(拓展单) 1-007-003-000 ========== + ErrorCode PAY_ORDER_EXTENSION_NOT_FOUND = new ErrorCode(1_007_003_000, "支付交易拓展单不存在"); + ErrorCode PAY_ORDER_EXTENSION_STATUS_IS_NOT_WAITING = new ErrorCode(1_007_003_001, "支付交易拓展单不处于待支付"); + ErrorCode PAY_ORDER_EXTENSION_IS_PAID = new ErrorCode(1_007_003_002, "订单已支付,请等待支付结果"); + + // ========== 支付模块(退款) 1-007-006-000 ========== + ErrorCode REFUND_PRICE_EXCEED = new ErrorCode(1_007_006_000, "退款金额超过订单可退款金额"); + ErrorCode REFUND_HAS_REFUNDING = new ErrorCode(1_007_006_002, "已经有退款在处理中"); + ErrorCode REFUND_EXISTS = new ErrorCode(1_007_006_003, "已经存在退款单"); + ErrorCode REFUND_NOT_FOUND = new ErrorCode(1_007_006_004, "支付退款单不存在"); + ErrorCode REFUND_STATUS_IS_NOT_WAITING = new ErrorCode(1_007_006_005, "支付退款单不处于待退款"); + + // ========== 钱包模块 1-007-007-000 ========== + ErrorCode WALLET_NOT_FOUND = new ErrorCode(1_007_007_000, "用户钱包不存在"); + ErrorCode WALLET_BALANCE_NOT_ENOUGH = new ErrorCode(1_007_007_001, "钱包余额不足"); + ErrorCode WALLET_TRANSACTION_NOT_FOUND = new ErrorCode(1_007_007_002, "未找到对应的钱包交易"); + ErrorCode WALLET_REFUND_EXIST = new ErrorCode(1_007_007_003, "已经存在钱包退款"); + ErrorCode WALLET_FREEZE_PRICE_NOT_ENOUGH = new ErrorCode(1_007_007_004, "钱包冻结余额不足"); + + // ========== 钱包充值模块 1-007-008-000 ========== + ErrorCode WALLET_RECHARGE_NOT_FOUND = new ErrorCode(1_007_008_000, "钱包充值记录不存在"); + ErrorCode WALLET_RECHARGE_UPDATE_PAID_STATUS_NOT_UNPAID = new ErrorCode(1_007_008_001, "钱包充值更新支付状态失败,钱包充值记录不是【未支付】状态"); + ErrorCode WALLET_RECHARGE_UPDATE_PAID_PAY_ORDER_ID_ERROR = new ErrorCode(1_007_008_002, "钱包充值更新支付状态失败,支付单编号不匹配"); + ErrorCode WALLET_RECHARGE_UPDATE_PAID_PAY_ORDER_STATUS_NOT_SUCCESS = new ErrorCode(1_007_008_003, "钱包充值更新支付状态失败,支付单状态不是【支付成功】状态"); + ErrorCode WALLET_RECHARGE_UPDATE_PAID_PAY_PRICE_NOT_MATCH = new ErrorCode(1_007_008_004, "钱包充值更新支付状态失败,支付单金额不匹配"); + ErrorCode WALLET_RECHARGE_REFUND_FAIL_NOT_PAID = new ErrorCode(1_007_008_005, "钱包发起退款失败,钱包充值订单未支付"); + ErrorCode WALLET_RECHARGE_REFUND_FAIL_REFUNDED = new ErrorCode(1_007_008_006, "钱包发起退款失败,钱包充值订单已退款"); + ErrorCode WALLET_RECHARGE_REFUND_BALANCE_NOT_ENOUGH = new ErrorCode(1_007_008_007, "钱包发起退款失败,钱包余额不足"); + ErrorCode WALLET_RECHARGE_REFUND_FAIL_REFUND_ORDER_ID_ERROR = new ErrorCode(1_007_008_008, "钱包退款更新失败,钱包退款单编号不匹配"); + ErrorCode WALLET_RECHARGE_REFUND_FAIL_REFUND_NOT_FOUND = new ErrorCode(1_007_008_009, "钱包退款更新失败,退款订单不存在"); + ErrorCode WALLET_RECHARGE_REFUND_FAIL_REFUND_PRICE_NOT_MATCH = new ErrorCode(1_007_008_010, "钱包退款更新失败,退款单金额不匹配"); + ErrorCode WALLET_RECHARGE_PACKAGE_NOT_FOUND = new ErrorCode(1_007_008_011, "钱包充值套餐不存在"); + ErrorCode WALLET_RECHARGE_PACKAGE_IS_DISABLE = new ErrorCode(1_007_008_012, "钱包充值套餐已禁用"); + ErrorCode WALLET_RECHARGE_PACKAGE_NAME_EXISTS = new ErrorCode(1_007_008_013, "钱包充值套餐名称已存在"); + + // ========== 转账模块 1-007-009-000 ========== + ErrorCode PAY_TRANSFER_NOT_FOUND = new ErrorCode(1_007_009_001, "转账单不存在"); + ErrorCode PAY_TRANSFER_CREATE_CHANNEL_NOT_MATCH = new ErrorCode(1_007_009_002, "转账发起失败,原因:两次相同转账请求的类型不匹配"); + ErrorCode PAY_TRANSFER_CREATE_PRICE_NOT_MATCH = new ErrorCode(1_007_009_003, "转账发起失败,原因:两次相同转账请求的金额不匹配"); + ErrorCode PAY_TRANSFER_CREATE_FAIL_STATUS_NOT_CLOSED = new ErrorCode(1_007_009_004, "转账发起失败,原因:已经存在相同的转账单,且状态不是已关闭"); + ErrorCode PAY_TRANSFER_NOTIFY_FAIL_STATUS_IS_NOT_WAITING = new ErrorCode(1_007_009_006, "通知转账结果失败,原因:转账单不处于待转账"); + ErrorCode PAY_TRANSFER_NOTIFY_FAIL_STATUS_NOT_WAITING_OR_PROCESSING = new ErrorCode(1_007_009_007, "通知转账结果失败,原因:转账单不处于待转账或转账中"); + + // ========== 示例订单 1-007-900-000 ========== + ErrorCode DEMO_ORDER_NOT_FOUND = new ErrorCode(1_007_900_000, "示例订单不存在"); + ErrorCode DEMO_ORDER_UPDATE_PAID_STATUS_NOT_UNPAID = new ErrorCode(1_007_900_001, "示例订单更新支付状态失败,订单不是【未支付】状态"); + ErrorCode DEMO_ORDER_UPDATE_PAID_FAIL_PAY_ORDER_ID_ERROR = new ErrorCode(1_007_900_002, "示例订单更新支付状态失败,支付单编号不匹配"); + ErrorCode DEMO_ORDER_UPDATE_PAID_FAIL_PAY_ORDER_STATUS_NOT_SUCCESS = new ErrorCode(1_007_900_003, "示例订单更新支付状态失败,支付单状态不是【支付成功】状态"); + ErrorCode DEMO_ORDER_UPDATE_PAID_FAIL_PAY_PRICE_NOT_MATCH = new ErrorCode(1_007_900_004, "示例订单更新支付状态失败,支付单金额不匹配"); + ErrorCode DEMO_ORDER_REFUND_FAIL_NOT_PAID = new ErrorCode(1_007_900_005, "发起退款失败,示例订单未支付"); + ErrorCode DEMO_ORDER_REFUND_FAIL_REFUNDED = new ErrorCode(1_007_900_006, "发起退款失败,示例订单已退款"); + ErrorCode DEMO_ORDER_REFUND_FAIL_REFUND_NOT_FOUND = new ErrorCode(1_007_900_007, "发起退款失败,退款订单不存在"); + ErrorCode DEMO_ORDER_REFUND_FAIL_REFUND_NOT_SUCCESS = new ErrorCode(1_007_900_008, "发起退款失败,退款订单未退款成功"); + ErrorCode DEMO_ORDER_REFUND_FAIL_REFUND_ORDER_ID_ERROR = new ErrorCode(1_007_900_009, "发起退款失败,退款单编号不匹配"); + ErrorCode DEMO_ORDER_REFUND_FAIL_REFUND_PRICE_NOT_MATCH = new ErrorCode(1_007_900_010, "发起退款失败,退款单金额不匹配"); + + // ========== 示例提现单 1-007-901-000 ========== + ErrorCode DEMO_WITHDRAW_NOT_FOUND = new ErrorCode(1_007_901_000, "示例提现单不存在"); + ErrorCode DEMO_WITHDRAW_UPDATE_STATUS_FAIL_PAY_TRANSFER_ID_ERROR = new ErrorCode(1_007_901_001, "更新示例提现单状态失败,支付转账单编号不匹配"); + ErrorCode DEMO_WITHDRAW_UPDATE_STATUS_FAIL_PAY_TRANSFER_STATUS_NOT_SUCCESS_OR_CLOSED = new ErrorCode(1_007_901_002, "更新示例提现单状态失败,支付转账单状态不是【转账成功】或【转账失败】状态"); + ErrorCode DEMO_WITHDRAW_UPDATE_STATUS_FAIL_PAY_PRICE_NOT_MATCH = new ErrorCode(1_007_901_003, "更新示例提现单状态失败,支付转账单金额不匹配"); + ErrorCode DEMO_WITHDRAW_UPDATE_STATUS_FAIL_PAY_MERCHANT_EXISTS = new ErrorCode(1_007_901_004, "更新示例提现单状态失败,支付转账单商户订单号不匹配"); + ErrorCode DEMO_WITHDRAW_UPDATE_STATUS_FAIL_PAY_CHANNEL_NOT_MATCH = new ErrorCode(1_007_901_005, "更新示例提现单状态失败,支付转账单渠道不匹配"); + ErrorCode DEMO_WITHDRAW_TRANSFER_FAIL_STATUS_NOT_WAITING_OR_CLOSED = new ErrorCode(1_007_901_008, "发起转账失败,原因:示例提现单状态不是【等待提现】或【提现关闭】"); + +} diff --git a/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/enums/MessageTemplateConstants.java b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/enums/MessageTemplateConstants.java new file mode 100644 index 0000000..9497d77 --- /dev/null +++ b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/enums/MessageTemplateConstants.java @@ -0,0 +1,14 @@ +package cn.iocoder.yudao.module.pay.enums; + +/** + * 通知模板枚举类 + * + * @author HUIHUI + */ +public interface MessageTemplateConstants { + + // ======================= 小程序订阅消息 ======================= + + String WXA_WALLET_RECHARGER_PAID = "充值成功通知"; + +} diff --git a/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/enums/PayChannelEnum.java b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/enums/PayChannelEnum.java new file mode 100644 index 0000000..2733282 --- /dev/null +++ b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/enums/PayChannelEnum.java @@ -0,0 +1,67 @@ +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; + +import java.util.Arrays; + +/** + * 支付渠道的编码的枚举 + * + * @author 芋道源码 + */ +@Getter +@AllArgsConstructor +public enum PayChannelEnum implements ArrayValuable { + + WX_PUB("wx_pub", "微信 JSAPI 支付"), // 公众号网页 + WX_LITE("wx_lite", "微信小程序支付"), + WX_APP("wx_app", "微信 App 支付"), + WX_NATIVE("wx_native", "微信 Native 支付"), + WX_WAP("wx_wap", "微信 Wap 网站支付"), // H5 网页 + WX_BAR("wx_bar", "微信付款码支付"), + + ALIPAY_PC("alipay_pc", "支付宝 PC 网站支付"), + ALIPAY_WAP("alipay_wap", "支付宝 Wap 网站支付"), + ALIPAY_APP("alipay_app", "支付宝App 支付"), + ALIPAY_QR("alipay_qr", "支付宝扫码支付"), + ALIPAY_BAR("alipay_bar", "支付宝条码支付"), + + MOCK("mock", "模拟支付"), + + WALLET("wallet", "钱包支付"); + + public static final String[] ARRAYS = Arrays.stream(values()).map(PayChannelEnum::getCode).toArray(String[]::new); + + /** + * 编码 + * + * 参考 支付渠道属性值 + */ + private final String code; + /** + * 名字 + */ + private final String name; + + @Override + public String[] array() { + 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/src/main/java/cn/iocoder/yudao/module/pay/enums/demo/PayDemoWithdrawStatusEnum.java b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/enums/demo/PayDemoWithdrawStatusEnum.java new file mode 100644 index 0000000..d65bd3b --- /dev/null +++ b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/enums/demo/PayDemoWithdrawStatusEnum.java @@ -0,0 +1,42 @@ +package cn.iocoder.yudao.module.pay.enums.demo; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +import java.util.Objects; + +/** + * 示例提现单的状态枚举 + * + * @author jason + */ +@Getter +@AllArgsConstructor +public enum PayDemoWithdrawStatusEnum { + + WAITING(0, "等待提现"), + 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 isWaiting(Integer status) { + return Objects.equals(status, WAITING.getStatus()); + } + +} diff --git a/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/enums/demo/PayDemoWithdrawTypeEnum.java b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/enums/demo/PayDemoWithdrawTypeEnum.java new file mode 100644 index 0000000..c2d9469 --- /dev/null +++ b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/enums/demo/PayDemoWithdrawTypeEnum.java @@ -0,0 +1,39 @@ +package cn.iocoder.yudao.module.pay.enums.demo; + +import cn.iocoder.yudao.framework.common.core.ArrayValuable; +import lombok.AllArgsConstructor; +import lombok.Getter; + +import java.util.Arrays; + +/** + * 示例提现单的类型枚举 + * + * @author owen + */ +@AllArgsConstructor +@Getter +public enum PayDemoWithdrawTypeEnum implements ArrayValuable { + + WECHAT(2, "微信"), + ALIPAY(1, "支付宝"), + WALLET(3, "钱包"), + ; + + public static final Integer[] ARRAYS = Arrays.stream(values()).map(PayDemoWithdrawTypeEnum::getType).toArray(Integer[]::new); + + /** + * 类型 + */ + private final Integer type; + /** + * 名字 + */ + private final String name; + + @Override + public Integer[] array() { + return ARRAYS; + } + +} diff --git a/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/enums/notify/PayNotifyStatusEnum.java b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/enums/notify/PayNotifyStatusEnum.java new file mode 100644 index 0000000..b735598 --- /dev/null +++ b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/enums/notify/PayNotifyStatusEnum.java @@ -0,0 +1,32 @@ +package cn.iocoder.yudao.module.pay.enums.notify; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * 支付通知状态枚举 + * + * @author 芋道源码 + */ +@Getter +@AllArgsConstructor +public enum PayNotifyStatusEnum { + + WAITING(0, "等待通知"), + SUCCESS(10, "通知成功"), + FAILURE(20, "通知失败"), // 多次尝试,彻底失败 + REQUEST_SUCCESS(21, "请求成功,但是结果失败"), + REQUEST_FAILURE(22, "请求失败"), + + ; + + /** + * 状态 + */ + private final Integer status; + /** + * 名字 + */ + private final String name; + +} diff --git a/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/enums/notify/PayNotifyTypeEnum.java b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/enums/notify/PayNotifyTypeEnum.java new file mode 100644 index 0000000..873e015 --- /dev/null +++ b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/enums/notify/PayNotifyTypeEnum.java @@ -0,0 +1,29 @@ +package cn.iocoder.yudao.module.pay.enums.notify; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * 支付通知类型 + * + * @author 芋道源码 + */ +@Getter +@AllArgsConstructor +public enum PayNotifyTypeEnum { + + ORDER(1, "支付单"), + REFUND(2, "退款单"), + TRANSFER(3, "转账单") + ; + + /** + * 类型 + */ + private final Integer type; + /** + * 名字 + */ + private final String name; + +} diff --git a/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/enums/order/PayOrderStatusEnum.java b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/enums/order/PayOrderStatusEnum.java new file mode 100644 index 0000000..5b5be4b --- /dev/null +++ b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/enums/order/PayOrderStatusEnum.java @@ -0,0 +1,84 @@ +package cn.iocoder.yudao.module.pay.enums.order; + +import cn.iocoder.yudao.framework.common.core.ArrayValuable; +import cn.iocoder.yudao.framework.common.util.object.ObjectUtils; +import lombok.AllArgsConstructor; +import lombok.Getter; + +import java.util.Objects; + +/** + * 支付订单的状态枚举 + * + * @author 芋道源码 + */ +@Getter +@AllArgsConstructor +public enum PayOrderStatusEnum implements ArrayValuable { + + WAITING(0, "未支付"), + SUCCESS(10, "支付成功"), + REFUND(20, "已退款"), + CLOSED(30, "支付关闭"), // 注意:全部退款后,还是 REFUND 状态 + ; + + private final Integer status; + private final String name; + + @Override + public Integer[] array() { + return new Integer[0]; + } + + /** + * 判断是否等待支付 + * + * @param status 状态 + * @return 是否等待支付 + */ + public static boolean isWaiting(Integer status) { + return Objects.equals(status, WAITING.getStatus()); + } + + /** + * 判断是否支付成功 + * + * @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 isSuccessOrRefund(Integer status) { + return ObjectUtils.equalsAny(status, + SUCCESS.getStatus(), REFUND.getStatus()); + } + + /** + * 判断是否支付关闭 + * + * @param status 状态 + * @return 是否支付关闭 + */ + public static boolean isClosed(Integer status) { + return Objects.equals(status, CLOSED.getStatus()); + } + +} diff --git a/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/enums/refund/PayRefundStatusEnum.java b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/enums/refund/PayRefundStatusEnum.java new file mode 100644 index 0000000..4ae14cc --- /dev/null +++ b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/enums/refund/PayRefundStatusEnum.java @@ -0,0 +1,32 @@ +package cn.iocoder.yudao.module.pay.enums.refund; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +import java.util.Objects; + +/** + * 渠道的退款状态枚举 + * + * @author 芋道源码 + */ +@Getter +@AllArgsConstructor +public enum PayRefundStatusEnum { + + 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/src/main/java/cn/iocoder/yudao/module/pay/enums/transfer/PayTransferStatusEnum.java b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/enums/transfer/PayTransferStatusEnum.java new file mode 100644 index 0000000..7a345f7 --- /dev/null +++ b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/enums/transfer/PayTransferStatusEnum.java @@ -0,0 +1,67 @@ +package cn.iocoder.yudao.module.pay.enums.transfer; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +import java.util.Objects; + +/** + * 渠道的转账状态枚举 + * + * @author jason + */ +@Getter +@AllArgsConstructor +public enum PayTransferStatusEnum { + + 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 isWaiting(Integer status) { + return Objects.equals(status, WAITING.getStatus()); + } + + public static boolean isProcessing(Integer status) { + return Objects.equals(status, PROCESSING.getStatus()); + } + + /** + * 是否处于待转账或者转账中的状态 + * + * @param status 状态 + * @return 是否 + */ + public static boolean isWaitingOrProcessing(Integer status) { + return isWaiting(status) || isProcessing(status); + } + + /** + * 是否处于成功或者关闭中的状态 + * + * @param status 状态 + * @return 是否 + */ + public static boolean isSuccessOrClosed(Integer status) { + return isSuccess(status) || isClosed(status); + } + +} diff --git a/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/enums/wallet/PayWalletBizTypeEnum.java b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/enums/wallet/PayWalletBizTypeEnum.java new file mode 100644 index 0000000..f2987cf --- /dev/null +++ b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/enums/wallet/PayWalletBizTypeEnum.java @@ -0,0 +1,45 @@ +package cn.iocoder.yudao.module.pay.enums.wallet; + +import cn.iocoder.yudao.framework.common.core.ArrayValuable; +import lombok.AllArgsConstructor; +import lombok.Getter; + +import java.util.Arrays; + +/** + * 钱包交易业务分类 + * + * @author jason + */ +@AllArgsConstructor +@Getter +public enum PayWalletBizTypeEnum implements ArrayValuable { + + RECHARGE(1, "充值"), + RECHARGE_REFUND(2, "充值退款"), + PAYMENT(3, "支付"), + PAYMENT_REFUND(4, "支付退款"), + UPDATE_BALANCE(5, "更新余额"), + TRANSFER(6, "转账"); + + /** + * 业务分类 + */ + private final Integer type; + /** + * 说明 + */ + private final String description; + + public static final Integer[] ARRAYS = Arrays.stream(values()).map(PayWalletBizTypeEnum::getType).toArray(Integer[]::new); + + @Override + public Integer[] array() { + return ARRAYS; + } + + public static PayWalletBizTypeEnum valueOf(Integer type) { + return Arrays.stream(values()).filter(item -> item.getType().equals(type)).findFirst().orElse(null); + } + +} diff --git a/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/framework/job/config/PayJobConfiguration.java b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/framework/job/config/PayJobConfiguration.java new file mode 100644 index 0000000..cd6bd0b --- /dev/null +++ b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/framework/job/config/PayJobConfiguration.java @@ -0,0 +1,28 @@ +package cn.iocoder.yudao.module.pay.framework.job.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; + +import java.util.concurrent.ThreadPoolExecutor; + +@Configuration(proxyBeanMethods = false) +public class PayJobConfiguration { + + public static final String NOTIFY_THREAD_POOL_TASK_EXECUTOR = "NOTIFY_THREAD_POOL_TASK_EXECUTOR"; + + @Bean(NOTIFY_THREAD_POOL_TASK_EXECUTOR) + public ThreadPoolTaskExecutor notifyThreadPoolTaskExecutor() { + ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); + executor.setCorePoolSize(8); // 设置核心线程数 + executor.setMaxPoolSize(16); // 设置最大线程数 + executor.setKeepAliveSeconds(60); // 设置空闲时间 + executor.setQueueCapacity(100); // 设置队列大小 + executor.setThreadNamePrefix("notify-task-"); // 配置线程池的前缀 + executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy()); + // 进行加载 + executor.initialize(); + return executor; + } + +} diff --git a/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/framework/job/core/package-info.java b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/framework/job/core/package-info.java new file mode 100644 index 0000000..5a5a689 --- /dev/null +++ b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/framework/job/core/package-info.java @@ -0,0 +1,4 @@ +/** + * 占位 + */ +package cn.iocoder.yudao.module.pay.framework.job.core; diff --git a/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/framework/package-info.java b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/framework/package-info.java new file mode 100644 index 0000000..a128682 --- /dev/null +++ b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/framework/package-info.java @@ -0,0 +1,6 @@ +/** + * 属于 pay 模块的 framework 封装 + * + * @author 芋道源码 + */ +package cn.iocoder.yudao.module.pay.framework; diff --git a/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/config/PayConfiguration.java b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/config/PayConfiguration.java new file mode 100644 index 0000000..47c9b9f --- /dev/null +++ b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/config/PayConfiguration.java @@ -0,0 +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/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/config/PayProperties.java b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/config/PayProperties.java new file mode 100644 index 0000000..84905a5 --- /dev/null +++ b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/config/PayProperties.java @@ -0,0 +1,69 @@ +package cn.iocoder.yudao.module.pay.framework.pay.config; + +import lombok.Data; +import org.hibernate.validator.constraints.URL; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.validation.annotation.Validated; + +import jakarta.validation.constraints.NotEmpty; + +@ConfigurationProperties(prefix = "yudao.pay") +@Validated +@Data +public class PayProperties { + + private static final String ORDER_NO_PREFIX = "P"; + private static final String REFUND_NO_PREFIX = "R"; + + private static final String WALLET_PAY_APP_KEY_DEFAULT = "wallet"; + + /** + * 支付回调地址 + * + * 实际上,对应的 PayNotifyController 的 notifyOrder 方法的 URL + * + * 回调顺序:支付渠道(支付宝支付、微信支付) => yudao-module-pay 的 orderNotifyUrl 地址 => 业务的 PayAppDO.orderNotifyUrl 地址 + */ + @NotEmpty(message = "支付回调地址不能为空") + @URL(message = "支付回调地址的格式必须是 URL") + private String orderNotifyUrl; + + /** + * 退款回调地址 + * + * 实际上,对应的 PayNotifyController 的 notifyRefund 方法的 URL + * + * 回调顺序:支付渠道(支付宝支付、微信支付) => yudao-module-pay 的 refundNotifyUrl 地址 => 业务的 PayAppDO.notifyRefundUrl 地址 + */ + @NotEmpty(message = "支付回调地址不能为空") + @URL(message = "支付回调地址的格式必须是 URL") + private String refundNotifyUrl; + + /** + * 转账回调地址 + * + * 实际上,对应的 PayNotifyController 的 notifyTransfer 方法的 URL + * + * 回调顺序:支付渠道(支付宝支付、微信支付) => yudao-module-pay 的 transferNotifyUrl 地址 => 业务的 PayAppDO.transferNotifyUrl 地址 + */ + private String transferNotifyUrl; + + /** + * 支付订单 no 的前缀 + */ + @NotEmpty(message = "支付订单 no 的前缀不能为空") + private String orderNoPrefix = ORDER_NO_PREFIX; + + /** + * 退款订单 no 的前缀 + */ + @NotEmpty(message = "退款订单 no 的前缀不能为空") + private String refundNoPrefix = REFUND_NO_PREFIX; + + /** + * 钱包支付应用 AppKey + */ + @NotEmpty(message = "钱包支付应用 AppKey 不能为空") + private String walletPayAppKey = WALLET_PAY_APP_KEY_DEFAULT; + +} diff --git a/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/PayClient.java b/yudao-module-pay/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/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/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/PayClientConfig.java b/yudao-module-pay/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/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/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/PayClientFactory.java b/yudao-module-pay/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/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/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/dto/order/PayOrderRespDTO.java b/yudao-module-pay/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/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/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/dto/order/PayOrderUnifiedReqDTO.java b/yudao-module-pay/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/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/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/dto/refund/PayRefundRespDTO.java b/yudao-module-pay/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/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/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/dto/refund/PayRefundUnifiedReqDTO.java b/yudao-module-pay/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/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/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/dto/transfer/PayTransferRespDTO.java b/yudao-module-pay/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/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/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/dto/transfer/PayTransferUnifiedReqDTO.java b/yudao-module-pay/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/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/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/exception/PayClientException.java b/yudao-module-pay/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/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/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/impl/AbstractPayClient.java b/yudao-module-pay/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/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/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/impl/NonePayClientConfig.java b/yudao-module-pay/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/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/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/impl/PayClientFactoryImpl.java b/yudao-module-pay/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/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/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/impl/alipay/AbstractAlipayPayClient.java b/yudao-module-pay/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/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/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/impl/alipay/AlipayAppPayClient.java b/yudao-module-pay/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/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/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/impl/alipay/AlipayBarPayClient.java b/yudao-module-pay/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/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/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/impl/alipay/AlipayPayClientConfig.java b/yudao-module-pay/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/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/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/impl/alipay/AlipayPcPayClient.java b/yudao-module-pay/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/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/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/impl/alipay/AlipayQrPayClient.java b/yudao-module-pay/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/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/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/impl/alipay/AlipayWapPayClient.java b/yudao-module-pay/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/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/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/impl/mock/MockPayClient.java b/yudao-module-pay/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/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/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/impl/wallet/WalletPayClient.java b/yudao-module-pay/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/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/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/impl/weixin/AbstractWxPayClient.java b/yudao-module-pay/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/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/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/impl/weixin/WxAppPayClient.java b/yudao-module-pay/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/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/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/impl/weixin/WxBarPayClient.java b/yudao-module-pay/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/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/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/impl/weixin/WxLitePayClient.java b/yudao-module-pay/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/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/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/impl/weixin/WxNativePayClient.java b/yudao-module-pay/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/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/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/impl/weixin/WxPayClientConfig.java b/yudao-module-pay/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/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/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/impl/weixin/WxPubPayClient.java b/yudao-module-pay/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/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/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/impl/weixin/WxWapPayClient.java b/yudao-module-pay/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/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/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/enums/PayOrderDisplayModeEnum.java b/yudao-module-pay/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/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/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/package-info.java b/yudao-module-pay/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/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/src/main/java/cn/iocoder/yudao/module/pay/framework/web/config/PayWebConfiguration.java b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/framework/web/config/PayWebConfiguration.java new file mode 100644 index 0000000..4a9dbb4 --- /dev/null +++ b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/framework/web/config/PayWebConfiguration.java @@ -0,0 +1,24 @@ +package cn.iocoder.yudao.module.pay.framework.web.config; + +import cn.iocoder.yudao.framework.swagger.config.YudaoSwaggerAutoConfiguration; +import org.springdoc.core.models.GroupedOpenApi; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * pay 模块的 web 组件的 Configuration + * + * @author 芋道源码 + */ +@Configuration(proxyBeanMethods = false) +public class PayWebConfiguration { + + /** + * pay 模块的 API 分组 + */ + @Bean + public GroupedOpenApi payGroupedOpenApi() { + return YudaoSwaggerAutoConfiguration.buildGroupedOpenApi("pay"); + } + +} diff --git a/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/framework/web/package-info.java b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/framework/web/package-info.java new file mode 100644 index 0000000..03bfad9 --- /dev/null +++ b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/framework/web/package-info.java @@ -0,0 +1,4 @@ +/** + * pay 模块的 web 配置 + */ +package cn.iocoder.yudao.module.pay.framework.web; diff --git a/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/job/notify/PayNotifyJob.java b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/job/notify/PayNotifyJob.java new file mode 100644 index 0000000..e95bb0a --- /dev/null +++ b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/job/notify/PayNotifyJob.java @@ -0,0 +1,31 @@ +package cn.iocoder.yudao.module.pay.job.notify; + +import cn.iocoder.yudao.framework.quartz.core.handler.JobHandler; +import cn.iocoder.yudao.framework.tenant.core.job.TenantJob; +import cn.iocoder.yudao.module.pay.service.notify.PayNotifyService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import jakarta.annotation.Resource; + +/** + * 支付通知 Job + * 通过不断扫描待通知的 PayNotifyTaskDO 记录,回调业务线的回调接口 + * + * @author 芋道源码 + */ +@Component +@Slf4j +public class PayNotifyJob implements JobHandler { + + @Resource + private PayNotifyService payNotifyService; + + @Override + @TenantJob + public String execute(String param) throws Exception { + int notifyCount = payNotifyService.executeNotify(); + return String.format("执行支付通知 %s 个", notifyCount); + } + +} diff --git a/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/job/order/PayOrderExpireJob.java b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/job/order/PayOrderExpireJob.java new file mode 100644 index 0000000..8f95737 --- /dev/null +++ b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/job/order/PayOrderExpireJob.java @@ -0,0 +1,31 @@ +package cn.iocoder.yudao.module.pay.job.order; + +import cn.hutool.core.util.StrUtil; +import cn.iocoder.yudao.framework.quartz.core.handler.JobHandler; +import cn.iocoder.yudao.framework.tenant.core.job.TenantJob; +import cn.iocoder.yudao.module.pay.service.order.PayOrderService; +import org.springframework.stereotype.Component; + +import jakarta.annotation.Resource; + +/** + * 支付订单的过期 Job + * + * 支付超过过期时间时,支付渠道是不会通知进行过期,所以需要定时进行过期关闭。 + * + * @author 芋道源码 + */ +@Component +public class PayOrderExpireJob implements JobHandler { + + @Resource + private PayOrderService orderService; + + @Override + @TenantJob + public String execute(String param) { + int count = orderService.expireOrder(); + return StrUtil.format("支付过期 {} 个", count); + } + +} diff --git a/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/job/order/PayOrderSyncJob.java b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/job/order/PayOrderSyncJob.java new file mode 100644 index 0000000..40fcfe9 --- /dev/null +++ b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/job/order/PayOrderSyncJob.java @@ -0,0 +1,43 @@ +package cn.iocoder.yudao.module.pay.job.order; + +import cn.hutool.core.util.StrUtil; +import cn.iocoder.yudao.framework.quartz.core.handler.JobHandler; +import cn.iocoder.yudao.framework.tenant.core.job.TenantJob; +import cn.iocoder.yudao.module.pay.service.order.PayOrderService; +import org.springframework.stereotype.Component; + +import jakarta.annotation.Resource; +import java.time.Duration; +import java.time.LocalDateTime; + +/** + * 支付订单的同步 Job + * + * 由于支付订单的状态,是由支付渠道异步通知进行同步,考虑到异步通知可能会失败(小概率),所以需要定时进行同步。 + * + * @author 芋道源码 + */ +@Component +public class PayOrderSyncJob implements JobHandler { + + /** + * 同步创建时间在 N 分钟之内的订单 + * + * 为什么同步 10 分钟之内的订单? + * 因为一个订单发起支付,到支付成功,大多数在 10 分钟内,需要保证轮询到。 + * 如果设置为 30、60 或者更大时间范围,会导致轮询的订单太多,影响性能。当然,你也可以根据自己的业务情况来处理。 + */ + private static final Duration CREATE_TIME_DURATION_BEFORE = Duration.ofMinutes(10); + + @Resource + private PayOrderService orderService; + + @Override + @TenantJob + public String execute(String param) { + LocalDateTime minCreateTime = LocalDateTime.now().minus(CREATE_TIME_DURATION_BEFORE); + int count = orderService.syncOrder(minCreateTime); + return StrUtil.format("同步支付订单 {} 个", count); + } + +} diff --git a/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/job/refund/PayRefundSyncJob.java b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/job/refund/PayRefundSyncJob.java new file mode 100644 index 0000000..0d5c850 --- /dev/null +++ b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/job/refund/PayRefundSyncJob.java @@ -0,0 +1,31 @@ +package cn.iocoder.yudao.module.pay.job.refund; + +import cn.hutool.core.util.StrUtil; +import cn.iocoder.yudao.framework.quartz.core.handler.JobHandler; +import cn.iocoder.yudao.framework.tenant.core.job.TenantJob; +import cn.iocoder.yudao.module.pay.service.refund.PayRefundService; +import org.springframework.stereotype.Component; + +import jakarta.annotation.Resource; + +/** + * 退款订单的同步 Job + * + * 由于退款订单的状态,是由支付渠道异步通知进行同步,考虑到异步通知可能会失败(小概率),所以需要定时进行同步。 + * + * @author 芋道源码 + */ +@Component +public class PayRefundSyncJob implements JobHandler { + + @Resource + private PayRefundService refundService; + + @Override + @TenantJob + public String execute(String param) { + int count = refundService.syncRefund(); + return StrUtil.format("同步退款订单 {} 个", count); + } + +} diff --git a/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/job/transfer/PayTransferSyncJob.java b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/job/transfer/PayTransferSyncJob.java new file mode 100644 index 0000000..d8f1321 --- /dev/null +++ b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/job/transfer/PayTransferSyncJob.java @@ -0,0 +1,30 @@ +package cn.iocoder.yudao.module.pay.job.transfer; + +import cn.hutool.core.util.StrUtil; +import cn.iocoder.yudao.framework.quartz.core.handler.JobHandler; +import cn.iocoder.yudao.framework.tenant.core.job.TenantJob; +import cn.iocoder.yudao.module.pay.service.transfer.PayTransferService; +import org.springframework.stereotype.Component; + +import jakarta.annotation.Resource; + +/** + * 转账订单的同步 Job + * + * 由于转账订单的转账结果,有些渠道是异步通知进行同步的,考虑到异步通知可能会失败(小概率),所以需要定时进行同步。 + * + * @author jason + */ +@Component +public class PayTransferSyncJob implements JobHandler { + + @Resource + private PayTransferService transferService; + + @Override + @TenantJob + public String execute(String param) { + int count = transferService.syncTransfer(); + return StrUtil.format("同步转账订单 {} 个", count); + } +} diff --git a/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/package-info.java b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/package-info.java new file mode 100644 index 0000000..40ab66d --- /dev/null +++ b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/package-info.java @@ -0,0 +1,10 @@ +/** + * pay 模块,我们放支付业务,提供业务的支付能力。 + * 例如说:商户、应用、支付、退款等等 + * + * 1. Controller URL:以 /pay/ 开头,避免和其它 Module 冲突 + * 2. DataObject 表名:以 pay_ 开头,方便在数据库中区分 + * + * 注意,由于 Pay 模块和 Trade 模块,容易重名,所以类名都加载 Pay 的前缀~ + */ +package cn.iocoder.yudao.module.pay; diff --git a/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/service/app/PayAppService.java b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/service/app/PayAppService.java new file mode 100644 index 0000000..d348f53 --- /dev/null +++ b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/service/app/PayAppService.java @@ -0,0 +1,115 @@ +package cn.iocoder.yudao.module.pay.service.app; + +import cn.iocoder.yudao.framework.common.exception.ServiceException; +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; +import cn.iocoder.yudao.module.pay.controller.admin.app.vo.PayAppCreateReqVO; +import cn.iocoder.yudao.module.pay.controller.admin.app.vo.PayAppPageReqVO; +import cn.iocoder.yudao.module.pay.controller.admin.app.vo.PayAppUpdateReqVO; +import cn.iocoder.yudao.module.pay.dal.dataobject.app.PayAppDO; +import jakarta.validation.Valid; + +import java.util.Collection; +import java.util.List; +import java.util.Map; + +/** + * 支付应用 Service 接口 + * + * @author 芋艿 + */ +public interface PayAppService { + + /** + * 创建支付应用 + * + * @param createReqVO 创建 + * @return 编号 + */ + Long createApp(@Valid PayAppCreateReqVO createReqVO); + + /** + * 更新支付应用 + * + * @param updateReqVO 更新 + */ + void updateApp(@Valid PayAppUpdateReqVO updateReqVO); + + /** + * 修改应用状态 + * + * @param id 应用编号 + * @param status 状态 + */ + void updateAppStatus(Long id, Integer status); + + /** + * 删除支付应用 + * + * @param id 编号 + */ + void deleteApp(Long id); + + /** + * 获得支付应用 + * + * @param id 编号 + * @return 支付应用 + */ + PayAppDO getApp(Long id); + + /** + * 获得支付应用列表 + * + * @param ids 编号 + * @return 支付应用列表 + */ + List getAppList(Collection ids); + + /** + * 获得支付应用列表 + * + * @return 支付应用列表 + */ + List getAppList(); + + /** + * 获得支付应用分页 + * + * @param pageReqVO 分页查询 + * @return 支付应用分页 + */ + PageResult getAppPage(PayAppPageReqVO pageReqVO); + + /** + * 获得指定编号的商户 Map + * + * @param ids 应用编号集合 + * @return 商户 Map + */ + default Map getAppMap(Collection ids) { + List list = getAppList(ids); + return CollectionUtils.convertMap(list, PayAppDO::getId); + } + + /** + * 支付应用的合法性 + *

+ * 如果不合法,抛出 {@link ServiceException} 业务异常 + * + * @param id 应用编号 + * @return 应用 + */ + PayAppDO validPayApp(Long id); + + /** + * 支付应用的合法性 + *

+ * 如果不合法,抛出 {@link ServiceException} 业务异常 + * + * @param appKey 应用标识 + * @return 应用 + */ + PayAppDO validPayApp(String appKey); + +} diff --git a/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/service/app/PayAppServiceImpl.java b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/service/app/PayAppServiceImpl.java new file mode 100644 index 0000000..67fd17b --- /dev/null +++ b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/service/app/PayAppServiceImpl.java @@ -0,0 +1,167 @@ +package cn.iocoder.yudao.module.pay.service.app; + +import cn.hutool.core.collection.CollUtil; +import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.module.pay.controller.admin.app.vo.PayAppCreateReqVO; +import cn.iocoder.yudao.module.pay.controller.admin.app.vo.PayAppPageReqVO; +import cn.iocoder.yudao.module.pay.controller.admin.app.vo.PayAppUpdateReqVO; +import cn.iocoder.yudao.module.pay.convert.app.PayAppConvert; +import cn.iocoder.yudao.module.pay.dal.dataobject.app.PayAppDO; +import cn.iocoder.yudao.module.pay.dal.mysql.app.PayAppMapper; +import cn.iocoder.yudao.module.pay.enums.ErrorCodeConstants; +import cn.iocoder.yudao.module.pay.service.order.PayOrderService; +import cn.iocoder.yudao.module.pay.service.refund.PayRefundService; +import jakarta.annotation.Resource; +import org.springframework.context.annotation.Lazy; +import org.springframework.stereotype.Service; +import org.springframework.validation.annotation.Validated; + +import java.util.Collection; +import java.util.Collections; +import java.util.List; + +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; +import static cn.iocoder.yudao.module.pay.enums.ErrorCodeConstants.*; + +/** + * 支付应用 Service 实现类 + * + * @author aquan + */ +@Service +@Validated +public class PayAppServiceImpl implements PayAppService { + + @Resource + private PayAppMapper appMapper; + + @Resource + @Lazy // 延迟加载,避免循环依赖报错 + private PayOrderService orderService; + @Resource + @Lazy // 延迟加载,避免循环依赖报错 + private PayRefundService refundService; + + @Override + public Long createApp(PayAppCreateReqVO createReqVO) { + // 验证 appKey 是否重复 + validateAppKeyUnique(null, createReqVO.getAppKey()); + + // 插入 + PayAppDO app = PayAppConvert.INSTANCE.convert(createReqVO); + appMapper.insert(app); + // 返回 + return app.getId(); + } + + @Override + public void updateApp(PayAppUpdateReqVO updateReqVO) { + // 校验存在 + validateAppExists(updateReqVO.getId()); + // 验证 appKey 是否重复 + validateAppKeyUnique(updateReqVO.getId(), updateReqVO.getAppKey()); + + // 更新 + PayAppDO updateObj = PayAppConvert.INSTANCE.convert(updateReqVO); + appMapper.updateById(updateObj); + } + + void validateAppKeyUnique(Long id, String appKey) { + PayAppDO app = appMapper.selectByAppKey(appKey); + if (app == null) { + return; + } + // 如果 id 为空,说明不用比较是否为相同 appKey 的应用 + if (id == null) { + throw exception(APP_KEY_EXISTS); + } + if (!app.getId().equals(id)) { + throw exception(APP_KEY_EXISTS); + } + } + + @Override + public void updateAppStatus(Long id, Integer status) { + // 校验商户存在 + validateAppExists(id); + // 更新状态 + appMapper.updateById(new PayAppDO().setId(id).setStatus(status)); + } + + @Override + public void deleteApp(Long id) { + // 校验存在 + validateAppExists(id); + // 校验关联数据是否存在 + if (orderService.getOrderCountByAppId(id) > 0) { + throw exception(APP_EXIST_ORDER_CANT_DELETE); + } + if (refundService.getRefundCountByAppId(id) > 0) { + throw exception(APP_EXIST_REFUND_CANT_DELETE); + } + + // 删除 + appMapper.deleteById(id); + } + + private void validateAppExists(Long id) { + if (appMapper.selectById(id) == null) { + throw exception(APP_NOT_FOUND); + } + } + + @Override + public PayAppDO getApp(Long id) { + return appMapper.selectById(id); + } + + @Override + public List getAppList(Collection ids) { + if (CollUtil.isEmpty(ids)) { + return Collections.emptyList(); + } + return appMapper.selectBatchIds(ids); + } + + @Override + public List getAppList() { + return appMapper.selectList(); + } + + @Override + public PageResult getAppPage(PayAppPageReqVO pageReqVO) { + return appMapper.selectPage(pageReqVO); + } + + @Override + public PayAppDO validPayApp(Long appId) { + PayAppDO app = appMapper.selectById(appId); + return validatePayApp(app); + } + + @Override + public PayAppDO validPayApp(String appKey) { + PayAppDO app = appMapper.selectByAppKey(appKey); + return validatePayApp(app); + } + + /** + * 校验支付应用实体的有效性:存在 + 开启 + * + * @param app 待校验的支付应用实体 + * @return 校验通过的支付应用实体 + */ + private PayAppDO validatePayApp(PayAppDO app) { + // 校验是否存在 + if (app == null) { + throw exception(ErrorCodeConstants.APP_NOT_FOUND); + } + // 校验是否禁用 + if (CommonStatusEnum.isDisable(app.getStatus())) { + throw exception(ErrorCodeConstants.APP_IS_DISABLE); + } + return app; + } + +} diff --git a/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/service/channel/PayChannelService.java b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/service/channel/PayChannelService.java new file mode 100644 index 0000000..c397986 --- /dev/null +++ b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/service/channel/PayChannelService.java @@ -0,0 +1,104 @@ +package cn.iocoder.yudao.module.pay.service.channel; + +import cn.iocoder.yudao.framework.common.exception.ServiceException; +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; + +import jakarta.validation.Valid; +import java.util.Collection; +import java.util.List; + +/** + * 支付渠道 Service 接口 + * + * @author aquan + */ +public interface PayChannelService { + + /** + * 创建支付渠道 + * + * @param createReqVO 创建信息 + * @return 编号 + */ + Long createChannel(@Valid PayChannelCreateReqVO createReqVO); + + /** + * 更新支付渠道 + * + * @param updateReqVO 更新信息 + */ + void updateChannel(@Valid PayChannelUpdateReqVO updateReqVO); + + /** + * 删除支付渠道 + * + * @param id 编号 + */ + void deleteChannel(Long id); + + /** + * 获得支付渠道 + * + * @param id 编号 + * @return 支付渠道 + */ + PayChannelDO getChannel(Long id); + + /** + * 根据支付应用 ID 集合,获得支付渠道列表 + * + * @param appIds 应用编号集合 + * @return 支付渠道列表 + */ + List getChannelListByAppIds(Collection appIds); + + /** + * 根据条件获取渠道 + * + * @param appId 应用编号 + * @param code 渠道编码 + * @return 数量 + */ + PayChannelDO getChannelByAppIdAndCode(Long appId, String code); + + /** + * 支付渠道的合法性 + * + * 如果不合法,抛出 {@link ServiceException} 业务异常 + * + * @param id 渠道编号 + * @return 渠道信息 + */ + PayChannelDO validPayChannel(Long id); + + /** + * 支付渠道的合法性 + * + * 如果不合法,抛出 {@link ServiceException} 业务异常 + * + * @param appId 应用编号 + * @param code 支付渠道 + * @return 渠道信息 + */ + PayChannelDO validPayChannel(Long appId, String code); + + /** + * 获得指定应用的开启的渠道列表 + * + * @param appId 应用编号 + * @return 渠道列表 + */ + List getEnableChannelList(Long appId); + + /** + * 获得指定编号的支付客户端 + * + * @param id 编号 + * @return 支付客户端 + */ + PayClient getPayClient(Long id); + +} diff --git a/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/service/channel/PayChannelServiceImpl.java b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/service/channel/PayChannelServiceImpl.java new file mode 100644 index 0000000..56ff5e5 --- /dev/null +++ b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/service/channel/PayChannelServiceImpl.java @@ -0,0 +1,165 @@ +package cn.iocoder.yudao.module.pay.service.channel; + +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.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.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; +import org.springframework.stereotype.Service; +import org.springframework.validation.annotation.Validated; + +import java.util.Collection; +import java.util.List; + +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; +import static cn.iocoder.yudao.module.pay.enums.ErrorCodeConstants.*; + +/** + * 支付渠道 Service 实现类 + * + * @author aquan + */ +@Service +@Slf4j +@Validated +public class PayChannelServiceImpl implements PayChannelService { + + @Resource + private PayClientFactory payClientFactory; + + @Resource + private PayChannelMapper payChannelMapper; + + @Resource + private Validator validator; + + @Override + public Long createChannel(PayChannelCreateReqVO reqVO) { + // 断言是否有重复的 + PayChannelDO dbChannel = getChannelByAppIdAndCode(reqVO.getAppId(), reqVO.getCode()); + if (dbChannel != null) { + throw exception(CHANNEL_EXIST_SAME_CHANNEL_ERROR); + } + + // 新增渠道 + PayChannelDO channel = PayChannelConvert.INSTANCE.convert(reqVO) + .setConfig(parseConfig(reqVO.getCode(), reqVO.getConfig())); + payChannelMapper.insert(channel); + return channel.getId(); + } + + @Override + public void updateChannel(PayChannelUpdateReqVO updateReqVO) { + // 校验存在 + PayChannelDO dbChannel = validateChannelExists(updateReqVO.getId()); + + // 更新 + PayChannelDO channel = PayChannelConvert.INSTANCE.convert(updateReqVO) + .setConfig(parseConfig(dbChannel.getCode(), updateReqVO.getConfig())); + payChannelMapper.updateById(channel); + } + + /** + * 解析并校验配置 + * + * @param code 渠道编码 + * @param configStr 配置 + * @return 支付配置 + */ + private PayClientConfig parseConfig(String code, String configStr) { + // 解析配置 + Class payClass = PayChannelEnum.isAlipay(configStr) ? AlipayPayClientConfig.class + : PayChannelEnum.isWeixin(configStr) ? WxPayClientConfig.class + : NonePayClientConfig.class; + if (ObjectUtil.isNull(payClass)) { + throw exception(CHANNEL_NOT_FOUND); + } + PayClientConfig config = JsonUtils.parseObject2(configStr, payClass); + Assert.notNull(config); + + // 验证参数 + config.validate(validator); + return config; + } + + @Override + public void deleteChannel(Long id) { + // 校验存在 + validateChannelExists(id); + + // 删除 + payChannelMapper.deleteById(id); + } + + private PayChannelDO validateChannelExists(Long id) { + PayChannelDO channel = payChannelMapper.selectById(id); + if (channel == null) { + throw exception(CHANNEL_NOT_FOUND); + } + return channel; + } + + @Override + public PayChannelDO getChannel(Long id) { + return payChannelMapper.selectById(id); + } + + @Override + public List getChannelListByAppIds(Collection appIds) { + return payChannelMapper.selectListByAppIds(appIds); + } + + @Override + public PayChannelDO getChannelByAppIdAndCode(Long appId, String code) { + return payChannelMapper.selectByAppIdAndCode(appId, code); + } + + @Override + public PayChannelDO validPayChannel(Long id) { + PayChannelDO channel = payChannelMapper.selectById(id); + validPayChannel(channel); + return channel; + } + + @Override + public PayChannelDO validPayChannel(Long appId, String code) { + PayChannelDO channel = payChannelMapper.selectByAppIdAndCode(appId, code); + validPayChannel(channel); + return channel; + } + + private void validPayChannel(PayChannelDO channel) { + if (channel == null) { + throw exception(CHANNEL_NOT_FOUND); + } + if (CommonStatusEnum.DISABLE.getStatus().equals(channel.getStatus())) { + throw exception(CHANNEL_IS_DISABLE); + } + } + + @Override + public List getEnableChannelList(Long appId) { + return payChannelMapper.selectListByAppId(appId, CommonStatusEnum.ENABLE.getStatus()); + } + + @Override + public PayClient getPayClient(Long id) { + PayChannelDO channel = validPayChannel(id); + return payClientFactory.createOrUpdatePayClient(id, channel.getCode(), channel.getConfig()); + } + +} diff --git a/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/service/demo/PayDemoOrderService.java b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/service/demo/PayDemoOrderService.java new file mode 100644 index 0000000..18de7b9 --- /dev/null +++ b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/service/demo/PayDemoOrderService.java @@ -0,0 +1,67 @@ +package cn.iocoder.yudao.module.pay.service.demo; + +import cn.iocoder.yudao.framework.common.pojo.PageParam; +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.module.pay.controller.admin.demo.vo.order.PayDemoOrderCreateReqVO; +import cn.iocoder.yudao.module.pay.dal.dataobject.demo.PayDemoOrderDO; + +import jakarta.validation.Valid; + +/** + * 示例订单 Service 接口 + * + * @author 芋道源码 + */ +public interface PayDemoOrderService { + + /** + * 创建示例订单 + * + * @param userId 用户编号 + * @param createReqVO 创建信息 + * @return 编号 + */ + Long createDemoOrder(Long userId, @Valid PayDemoOrderCreateReqVO createReqVO); + + /** + * 获得示例订单 + * + * @param id 编号 + * @return 示例订单 + */ + PayDemoOrderDO getDemoOrder(Long id); + + /** + * 获得示例订单分页 + * + * @param pageReqVO 分页查询 + * @return 示例订单分页 + */ + PageResult getDemoOrderPage(PageParam pageReqVO); + + /** + * 更新示例订单为已支付 + * + * @param id 编号 + * @param payOrderId 支付订单号 + */ + void updateDemoOrderPaid(Long id, Long payOrderId); + + /** + * 发起示例订单的退款 + * + * @param id 编号 + * @param userIp 用户编号 + */ + void refundDemoOrder(Long id, String userIp); + + /** + * 更新示例订单为已退款 + * + * @param id 编号 + * @param refundId 退款编号 + * @param payRefundId 退款订单号 + */ + void updateDemoOrderRefunded(Long id, String refundId, Long payRefundId); + +} diff --git a/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/service/demo/PayDemoOrderServiceImpl.java b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/service/demo/PayDemoOrderServiceImpl.java new file mode 100644 index 0000000..76571f3 --- /dev/null +++ b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/service/demo/PayDemoOrderServiceImpl.java @@ -0,0 +1,265 @@ +package cn.iocoder.yudao.module.pay.service.demo; + +import cn.hutool.core.lang.Assert; +import cn.hutool.core.util.ObjUtil; +import cn.hutool.core.util.ObjectUtil; +import cn.iocoder.yudao.framework.common.pojo.PageParam; +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.module.pay.api.order.PayOrderApi; +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.api.refund.PayRefundApi; +import cn.iocoder.yudao.module.pay.api.refund.dto.PayRefundCreateReqDTO; +import cn.iocoder.yudao.module.pay.api.refund.dto.PayRefundRespDTO; +import cn.iocoder.yudao.module.pay.controller.admin.demo.vo.order.PayDemoOrderCreateReqVO; +import cn.iocoder.yudao.module.pay.dal.dataobject.demo.PayDemoOrderDO; +import cn.iocoder.yudao.module.pay.dal.mysql.demo.PayDemoOrderMapper; +import cn.iocoder.yudao.module.pay.enums.order.PayOrderStatusEnum; +import cn.iocoder.yudao.module.pay.enums.refund.PayRefundStatusEnum; +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.validation.annotation.Validated; + +import java.time.Duration; +import java.time.LocalDateTime; +import java.util.HashMap; +import java.util.Map; + +import static cn.hutool.core.util.ObjectUtil.notEqual; +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; +import static cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.addTime; +import static cn.iocoder.yudao.framework.common.util.json.JsonUtils.toJsonString; +import static cn.iocoder.yudao.framework.common.util.servlet.ServletUtils.getClientIP; +import static cn.iocoder.yudao.module.pay.enums.ErrorCodeConstants.*; + +/** + * 示例订单 Service 实现类 + * + * @author 芋道源码 + */ +@Service +@Validated +@Slf4j +public class PayDemoOrderServiceImpl implements PayDemoOrderService { + + /** + * 接入的支付应用标识 + * + * 从 [支付管理 -> 应用信息] 里添加 + */ + private static final String PAY_APP_KEY = "demo"; + + /** + * 商品信息 Map + * + * key:商品编号 + * value:[商品名、商品价格] + */ + private final Map spuNames = new HashMap<>(); + + @Resource + private PayOrderApi payOrderApi; + @Resource + private PayRefundApi payRefundApi; + + @Resource + private PayDemoOrderMapper payDemoOrderMapper; + + public PayDemoOrderServiceImpl() { + spuNames.put(1L, new Object[]{"华为手机", 1}); + spuNames.put(2L, new Object[]{"小米电视", 10}); + spuNames.put(3L, new Object[]{"苹果手表", 100}); + spuNames.put(4L, new Object[]{"华硕笔记本", 1000}); + spuNames.put(5L, new Object[]{"蔚来汽车", 200000}); + } + + @Override + public Long createDemoOrder(Long userId, PayDemoOrderCreateReqVO createReqVO) { + // 1.1 获得商品 + Object[] spu = spuNames.get(createReqVO.getSpuId()); + Assert.notNull(spu, "商品({}) 不存在", createReqVO.getSpuId()); + String spuName = (String) spu[0]; + Integer price = (Integer) spu[1]; + // 1.2 插入 demo 订单 + PayDemoOrderDO demoOrder = new PayDemoOrderDO().setUserId(userId) + .setSpuId(createReqVO.getSpuId()).setSpuName(spuName) + .setPrice(price).setPayStatus(false).setRefundPrice(0); + payDemoOrderMapper.insert(demoOrder); + + // 2.1 创建支付单 + Long payOrderId = payOrderApi.createOrder(new PayOrderCreateReqDTO() + .setAppKey(PAY_APP_KEY).setUserIp(getClientIP()) // 支付应用 + .setMerchantOrderId(demoOrder.getId().toString()) // 业务的订单编号 + .setSubject(spuName).setBody("").setPrice(price) // 价格信息 + .setExpireTime(addTime(Duration.ofHours(2L)))); // 支付的过期时间 + // 2.2 更新支付单到 demo 订单 + payDemoOrderMapper.updateById(new PayDemoOrderDO().setId(demoOrder.getId()) + .setPayOrderId(payOrderId)); + return demoOrder.getId(); + } + + @Override + public PayDemoOrderDO getDemoOrder(Long id) { + return payDemoOrderMapper.selectById(id); + } + + @Override + public PageResult getDemoOrderPage(PageParam pageReqVO) { + return payDemoOrderMapper.selectPage(pageReqVO); + } + + @Override + public void updateDemoOrderPaid(Long id, Long payOrderId) { + // 1.1 校验订单是否存在 + PayDemoOrderDO order = payDemoOrderMapper.selectById(id); + if (order == null) { + log.error("[updateDemoOrderPaid][order({}) payOrder({}) 不存在订单,请进行处理!]", id, payOrderId); + throw exception(DEMO_ORDER_NOT_FOUND); + } + // 1.2 校验订单已支付 + if (order.getPayStatus()) { + // 特殊:支付单号相同,直接返回,说明重复回调 + if (ObjectUtil.equals(order.getPayOrderId(), payOrderId)) { + log.warn("[updateDemoOrderPaid][order({}) 已支付,且支付单号相同({}),直接返回]", order, payOrderId); + return; + } + // 异常:支付单号不同,说明支付单号错误 + log.error("[updateDemoOrderPaid][order({}) 支付单不匹配({}),请进行处理!]", + order, payOrderId); + throw exception(DEMO_ORDER_UPDATE_PAID_FAIL_PAY_ORDER_ID_ERROR); + } + + // 2. 校验支付订单的合法性 + PayOrderRespDTO payOrder = validatePayOrderPaid(order, payOrderId); + + // 3. 更新 PayDemoOrderDO 状态为已支付 + int updateCount = payDemoOrderMapper.updateByIdAndPayed(id, false, + new PayDemoOrderDO().setPayStatus(true).setPayTime(LocalDateTime.now()) + .setPayChannelCode(payOrder.getChannelCode())); + if (updateCount == 0) { + throw exception(DEMO_ORDER_UPDATE_PAID_STATUS_NOT_UNPAID); + } + } + + /** + * 校验支付订单的合法性 + * + * @param order 交易订单 + * @param payOrderId 支付订单编号 + * @return 支付订单 + */ + private PayOrderRespDTO validatePayOrderPaid(PayDemoOrderDO order, Long payOrderId) { + // 1. 校验支付单是否存在 + PayOrderRespDTO payOrder = payOrderApi.getOrder(payOrderId); + if (payOrder == null) { + log.error("[validatePayOrderPaid][order({}) payOrder({}) 不存在,请进行处理!]", order.getId(), payOrderId); + throw exception(PAY_ORDER_NOT_FOUND); + } + // 2.1 校验支付单已支付 + if (!PayOrderStatusEnum.isSuccess(payOrder.getStatus())) { + log.error("[validatePayOrderPaid][order({}) payOrder({}) 未支付,请进行处理!payOrder 数据是:{}]", + order.getId(), payOrderId, toJsonString(payOrder)); + throw exception(DEMO_ORDER_UPDATE_PAID_FAIL_PAY_ORDER_STATUS_NOT_SUCCESS); + } + // 2.1 校验支付金额一致 + if (notEqual(payOrder.getPrice(), order.getPrice())) { + log.error("[validatePayOrderPaid][order({}) payOrder({}) 支付金额不匹配,请进行处理!order 数据是:{},payOrder 数据是:{}]", + order.getId(), payOrderId, toJsonString(order), toJsonString(payOrder)); + throw exception(DEMO_ORDER_UPDATE_PAID_FAIL_PAY_PRICE_NOT_MATCH); + } + // 2.2 校验支付订单匹配(二次) + if (notEqual(payOrder.getMerchantOrderId(), order.getId().toString())) { + log.error("[validatePayOrderPaid][order({}) 支付单不匹配({}),请进行处理!payOrder 数据是:{}]", + order.getId(), payOrderId, toJsonString(payOrder)); + throw exception(DEMO_ORDER_UPDATE_PAID_FAIL_PAY_ORDER_ID_ERROR); + } + return payOrder; + } + + @Override + public void refundDemoOrder(Long id, String userIp) { + // 1. 校验订单是否可以退款 + PayDemoOrderDO order = validateDemoOrderCanRefund(id); + + // 2.1 生成退款单号 + // 一般来说,用户发起退款的时候,都会单独插入一个售后维权表,然后使用该表的 id 作为 refundId + // 这里我们是个简单的 demo,所以没有售后维权表,直接使用订单 id + "-refund" 来演示 + String refundId = order.getId() + "-refund"; + // 2.2 创建退款单 + Long payRefundId = payRefundApi.createRefund(new PayRefundCreateReqDTO() + .setAppKey(PAY_APP_KEY).setUserIp(getClientIP()) // 支付应用 + .setMerchantOrderId(String.valueOf(order.getId())) // 支付单号 + .setMerchantRefundId(refundId) + .setReason("想退钱").setPrice(order.getPrice()));// 价格信息 + // 2.3 更新退款单到 demo 订单 + payDemoOrderMapper.updateById(new PayDemoOrderDO().setId(id) + .setPayRefundId(payRefundId).setRefundPrice(order.getPrice())); + } + + private PayDemoOrderDO validateDemoOrderCanRefund(Long id) { + // 校验订单是否存在 + PayDemoOrderDO order = payDemoOrderMapper.selectById(id); + if (order == null) { + throw exception(DEMO_ORDER_NOT_FOUND); + } + // 校验订单是否支付 + if (!order.getPayStatus()) { + throw exception(DEMO_ORDER_REFUND_FAIL_NOT_PAID); + } + // 校验订单是否已退款 + if (order.getPayRefundId() != null) { + throw exception(DEMO_ORDER_REFUND_FAIL_REFUNDED); + } + return order; + } + + @Override + public void updateDemoOrderRefunded(Long id, String refundId, Long payRefundId) { + // 1. 校验并获得退款订单(可退款) + PayRefundRespDTO payRefund = validateDemoOrderCanRefunded(id, refundId, payRefundId); + // 2.2 更新退款单到 demo 订单 + payDemoOrderMapper.updateById(new PayDemoOrderDO().setId(id) + .setRefundTime(payRefund.getSuccessTime())); + } + + private PayRefundRespDTO validateDemoOrderCanRefunded(Long id, String refundId, Long payRefundId) { + // 1.1 校验示例订单 + // 一般来说,这里应该用 refundId 来查询退款单,然后再校验订单是否匹配 + // 这里我们是个简单的 demo,所以没有售后维权表,直接使用订单 id 来查询订单 + PayDemoOrderDO order = payDemoOrderMapper.selectById(id); + if (order == null) { + throw exception(DEMO_ORDER_NOT_FOUND); + } + // 1.2 校验退款订单匹配 + if (ObjUtil.notEqual(order.getPayRefundId(), payRefundId)) { + log.error("[validateDemoOrderCanRefunded][order({}) 退款单不匹配({}),请进行处理!order 数据是:{}]", + id, payRefundId, toJsonString(order)); + throw exception(DEMO_ORDER_REFUND_FAIL_REFUND_ORDER_ID_ERROR); + } + + // 2.1 校验退款订单 + PayRefundRespDTO payRefund = payRefundApi.getRefund(payRefundId); + if (payRefund == null) { + throw exception(DEMO_ORDER_REFUND_FAIL_REFUND_NOT_FOUND); + } + // 2.2 必须是退款成功状态 + if (!PayRefundStatusEnum.isSuccess(payRefund.getStatus())) { + throw exception(DEMO_ORDER_REFUND_FAIL_REFUND_NOT_SUCCESS); + } + // 2.3 校验退款金额一致 + if (notEqual(payRefund.getRefundPrice(), order.getPrice())) { + log.error("[validateDemoOrderCanRefunded][order({}) payRefund({}) 退款金额不匹配,请进行处理!order 数据是:{},payRefund 数据是:{}]", + id, payRefundId, toJsonString(order), toJsonString(payRefund)); + throw exception(DEMO_ORDER_REFUND_FAIL_REFUND_PRICE_NOT_MATCH); + } + // 2.4 校验退款订单匹配(二次) + if (notEqual(payRefund.getMerchantRefundId(), id.toString() + "-refund")) { + log.error("[validateDemoOrderCanRefunded][order({}) 退款单不匹配({}),请进行处理!payRefund 数据是:{}]", + id, payRefundId, toJsonString(payRefund)); + throw exception(DEMO_ORDER_REFUND_FAIL_REFUND_ORDER_ID_ERROR); + } + return payRefund; + } + +} diff --git a/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/service/demo/PayDemoWithdrawService.java b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/service/demo/PayDemoWithdrawService.java new file mode 100644 index 0000000..e8a5980 --- /dev/null +++ b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/service/demo/PayDemoWithdrawService.java @@ -0,0 +1,48 @@ +package cn.iocoder.yudao.module.pay.service.demo; + +import cn.iocoder.yudao.framework.common.pojo.PageParam; +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.module.pay.controller.admin.demo.vo.withdraw.PayDemoWithdrawCreateReqVO; +import cn.iocoder.yudao.module.pay.dal.dataobject.demo.PayDemoWithdrawDO; + +import jakarta.validation.Valid; + +/** + * 示例提现单 Service 接口 + * + * @author jason + */ +public interface PayDemoWithdrawService { + + /** + * 创建示例提现单 + * + * @param createReqVO 创建信息 + * @return 编号 + */ + Long createDemoWithdraw(@Valid PayDemoWithdrawCreateReqVO createReqVO); + + /** + * 提现单转账 + * + * @param id 提现单编号 + * @return 转账编号 + */ + Long transferDemoWithdraw(Long id); + + /** + * 获得示例提现单分页 + * + * @param pageVO 分页查询参数 + */ + PageResult getDemoWithdrawPage(PageParam pageVO); + + /** + * 更新示例提现单的状态 + * + * @param id 编号 + * @param payTransferId 转账单编号 + */ + void updateDemoWithdrawTransferred(Long id, Long payTransferId); + +} diff --git a/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/service/demo/PayDemoWithdrawServiceImpl.java b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/service/demo/PayDemoWithdrawServiceImpl.java new file mode 100644 index 0000000..b1a3b91 --- /dev/null +++ b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/service/demo/PayDemoWithdrawServiceImpl.java @@ -0,0 +1,195 @@ +package cn.iocoder.yudao.module.pay.service.demo; + +import cn.hutool.core.lang.Assert; +import cn.hutool.core.util.ObjectUtil; +import cn.iocoder.yudao.framework.common.pojo.PageParam; +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.module.pay.api.transfer.PayTransferApi; +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; +import cn.iocoder.yudao.module.pay.controller.admin.demo.vo.withdraw.PayDemoWithdrawCreateReqVO; +import cn.iocoder.yudao.module.pay.dal.dataobject.demo.PayDemoWithdrawDO; +import cn.iocoder.yudao.module.pay.dal.mysql.demo.PayDemoWithdrawMapper; +import cn.iocoder.yudao.module.pay.enums.PayChannelEnum; +import cn.iocoder.yudao.module.pay.enums.demo.PayDemoWithdrawStatusEnum; +import cn.iocoder.yudao.module.pay.enums.demo.PayDemoWithdrawTypeEnum; +import cn.iocoder.yudao.module.pay.enums.transfer.PayTransferStatusEnum; +import jakarta.annotation.Resource; +import jakarta.validation.Valid; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.validation.annotation.Validated; + +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; +import static cn.iocoder.yudao.framework.common.util.servlet.ServletUtils.getClientIP; +import static cn.iocoder.yudao.module.pay.enums.ErrorCodeConstants.*; + +/** + * 示例转账业务 Service 实现类 + * + * @author jason + */ +@Service +@Validated +@Slf4j +public class PayDemoWithdrawServiceImpl implements PayDemoWithdrawService { + + /** + * 接入的支付应用标识 + * + * 从 [支付管理 -> 应用信息] 里添加 + */ + private static final String PAY_APP_KEY = "demo"; + + @Resource + private PayDemoWithdrawMapper demoTransferMapper; + + @Resource + private PayTransferApi payTransferApi; + + @Override + public Long createDemoWithdraw(@Valid PayDemoWithdrawCreateReqVO reqVO) { + PayDemoWithdrawDO withdraw = BeanUtils.toBean(reqVO, PayDemoWithdrawDO.class) + .setTransferChannelCode(getTransferChannelCode(reqVO.getType())) + .setStatus(PayDemoWithdrawStatusEnum.WAITING.getStatus()); + demoTransferMapper.insert(withdraw); + return withdraw.getId(); + } + + @Override + public Long transferDemoWithdraw(Long id) { + // 1.1 校验提现单 + PayDemoWithdrawDO withdraw = validateDemoWithdrawCanTransfer(id); + // 1.2 特殊:如果是转账失败的情况,需要充值下 + if (PayDemoWithdrawStatusEnum.isClosed(withdraw.getStatus())) { + int updateCount = demoTransferMapper.updateByIdAndStatus(withdraw.getId(), withdraw.getStatus(), + new PayDemoWithdrawDO().setStatus(PayDemoWithdrawStatusEnum.WAITING.getStatus()).setTransferErrorMsg("")); + if (updateCount == 0) { + throw exception(DEMO_WITHDRAW_TRANSFER_FAIL_STATUS_NOT_WAITING_OR_CLOSED); + } + withdraw.setStatus(PayDemoWithdrawStatusEnum.WAITING.getStatus()); + } + + // 2.1 创建支付单 + PayTransferCreateReqDTO transferReqDTO = new PayTransferCreateReqDTO() + .setAppKey(PAY_APP_KEY).setChannelCode(withdraw.getTransferChannelCode()).setUserIp(getClientIP()) // 支付应用 + .setMerchantTransferId(String.valueOf(withdraw.getId())) // 业务的订单编号 + .setSubject(withdraw.getSubject()).setPrice(withdraw.getPrice()) // 价格信息 + .setUserAccount(withdraw.getUserAccount()).setUserName(withdraw.getUserName()); // 收款信息 + if (ObjectUtil.equal(withdraw.getType(), PayDemoWithdrawTypeEnum.WECHAT.getType())) { + // 注意:微信很特殊!提现需要写明用途!!! + transferReqDTO.setChannelExtras(PayTransferCreateReqDTO.buildWeiXinChannelExtra1000( + "测试活动", "测试奖励")); + } + PayTransferCreateRespDTO transferRespDTO = payTransferApi.createTransfer(transferReqDTO); + + // 2.2 更新转账单到 demo 示例提现单,并将状态更新为转账中 + demoTransferMapper.updateByIdAndStatus(withdraw.getId(), withdraw.getStatus(), + new PayDemoWithdrawDO().setPayTransferId(transferRespDTO.getId())); + return transferRespDTO.getId(); + } + + private PayDemoWithdrawDO validateDemoWithdrawCanTransfer(Long id) { + // 校验存在 + PayDemoWithdrawDO withdraw = demoTransferMapper.selectById(id); + if (withdraw == null) { + throw exception(DEMO_WITHDRAW_NOT_FOUND); + } + // 校验状态,只有等待中或转账失败的订单,才能发起转账 + if (!PayDemoWithdrawStatusEnum.isWaiting(withdraw.getStatus()) + && !PayDemoWithdrawStatusEnum.isClosed(withdraw.getStatus())) { + throw exception(DEMO_WITHDRAW_TRANSFER_FAIL_STATUS_NOT_WAITING_OR_CLOSED); + } + return withdraw; + } + + private String getTransferChannelCode(Integer type) { + if (ObjectUtil.equal(type, PayDemoWithdrawTypeEnum.ALIPAY.getType())) { + return PayChannelEnum.ALIPAY_PC.getCode(); + } + if (ObjectUtil.equal(type, PayDemoWithdrawTypeEnum.WECHAT.getType())) { + return PayChannelEnum.WX_LITE.getCode(); + } + if (ObjectUtil.equal(type, PayDemoWithdrawTypeEnum.WALLET.getType())) { + return PayChannelEnum.WALLET.getCode(); + } + throw new IllegalArgumentException("未知提现方式:" + type); + } + + @Override + public PageResult getDemoWithdrawPage(PageParam pageVO) { + return demoTransferMapper.selectPage(pageVO); + } + + @Override + public void updateDemoWithdrawTransferred(Long id, Long payTransferId) { + // 1.1 校验转账单是否存在 + PayDemoWithdrawDO withdraw = demoTransferMapper.selectById(id); + if (withdraw == null) { + log.error("[updateDemoWithdrawStatus][withdraw({}) payOrder({}) 不存在提现单,请进行处理!]", id, payTransferId); + throw exception(DEMO_WITHDRAW_NOT_FOUND); + } + // 1.2 校验转账单已成结束(成功或失败) + if (PayDemoWithdrawStatusEnum.isSuccess(withdraw.getStatus()) + || PayDemoWithdrawStatusEnum.isClosed(withdraw.getStatus())) { + // 特殊:转账单编号相同,直接返回,说明重复回调 + if (ObjectUtil.equal(withdraw.getPayTransferId(), payTransferId)) { + log.warn("[updateDemoWithdrawStatus][withdraw({}) 已结束,且转账单编号相同({}),直接返回]", withdraw, payTransferId); + return; + } + // 异常:转账单编号不同,说明转账单编号错误 + log.error("[updateDemoWithdrawStatus][withdraw({}) 转账单不匹配({}),请进行处理!]", withdraw, payTransferId); + throw exception(DEMO_WITHDRAW_UPDATE_STATUS_FAIL_PAY_TRANSFER_ID_ERROR); + } + + // 2. 校验提现单的合法性 + PayTransferRespDTO payTransfer = validateDemoTransferStatusCanUpdate(withdraw, payTransferId); + + // 3. 更新示例订单状态 + Integer newStatus = PayTransferStatusEnum.isSuccess(payTransfer.getStatus()) ? PayDemoWithdrawStatusEnum.SUCCESS.getStatus() : + PayTransferStatusEnum.isClosed(payTransfer.getStatus()) ? PayDemoWithdrawStatusEnum.CLOSED.getStatus() : null; + Assert.notNull(newStatus, "转账单状态({}) 不合法", payTransfer.getStatus()); + demoTransferMapper.updateByIdAndStatus(withdraw.getId(), withdraw.getStatus(), + new PayDemoWithdrawDO().setStatus(newStatus).setTransferTime(payTransfer.getSuccessTime()) + .setTransferErrorMsg(payTransfer.getChannelErrorMsg())); + } + + private PayTransferRespDTO validateDemoTransferStatusCanUpdate(PayDemoWithdrawDO withdraw, Long payTransferId) { + // 1. 校验转账单是否存在 + PayTransferRespDTO payTransfer = payTransferApi.getTransfer(payTransferId); + if (payTransfer == null) { + log.error("[validateDemoTransferStatusCanUpdate][withdraw({}) payTransfer({}) 不存在,请进行处理!]", withdraw.getId(), payTransferId); + throw exception(PAY_TRANSFER_NOT_FOUND); + } + + // 2.1 校验转账单已成功 + if (!PayTransferStatusEnum.isSuccessOrClosed(payTransfer.getStatus())) { + log.error("[validateDemoTransferStatusCanUpdate][withdraw({}) payTransfer({}) 未结束,请进行处理!payTransfer 数据是:{}]", + withdraw.getId(), payTransferId, JsonUtils.toJsonString(payTransfer)); + throw exception(DEMO_WITHDRAW_UPDATE_STATUS_FAIL_PAY_TRANSFER_STATUS_NOT_SUCCESS_OR_CLOSED); + } + // 2.2 校验转账金额一致 + if (ObjectUtil.notEqual(payTransfer.getPrice(), withdraw.getPrice())) { + log.error("[validateDemoTransferStatusCanUpdate][withdraw({}) payTransfer({}) 转账金额不匹配,请进行处理!withdraw 数据是:{},payTransfer 数据是:{}]", + withdraw.getId(), payTransferId, JsonUtils.toJsonString(withdraw), JsonUtils.toJsonString(payTransfer)); + throw exception(DEMO_WITHDRAW_UPDATE_STATUS_FAIL_PAY_PRICE_NOT_MATCH); + } + // 2.3 校验转账订单匹配(二次) + if (ObjectUtil.notEqual(payTransfer.getMerchantTransferId(), withdraw.getId().toString())) { + log.error("[validateDemoTransferStatusCanUpdate][withdraw({}) 转账单不匹配({}),请进行处理!payTransfer 数据是:{}]", + withdraw.getId(), payTransferId, JsonUtils.toJsonString(payTransfer)); + throw exception(DEMO_WITHDRAW_UPDATE_STATUS_FAIL_PAY_MERCHANT_EXISTS); + } + // 2.4 校验转账渠道一致 + if (ObjectUtil.notEqual(payTransfer.getChannelCode(), withdraw.getTransferChannelCode())) { + log.error("[validateDemoTransferStatusCanUpdate][withdraw({}) payTransfer({}) 转账渠道不匹配,请进行处理!withdraw 数据是:{},payTransfer 数据是:{}]", + withdraw.getId(), payTransferId, JsonUtils.toJsonString(withdraw), JsonUtils.toJsonString(payTransfer)); + throw exception(DEMO_WITHDRAW_UPDATE_STATUS_FAIL_PAY_CHANNEL_NOT_MATCH); + } + return payTransfer; + } + +} diff --git a/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/service/notify/PayNotifyService.java b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/service/notify/PayNotifyService.java new file mode 100644 index 0000000..ed93997 --- /dev/null +++ b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/service/notify/PayNotifyService.java @@ -0,0 +1,57 @@ +package cn.iocoder.yudao.module.pay.service.notify; + +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.module.pay.controller.admin.notify.vo.PayNotifyTaskPageReqVO; +import cn.iocoder.yudao.module.pay.dal.dataobject.notify.PayNotifyLogDO; +import cn.iocoder.yudao.module.pay.dal.dataobject.notify.PayNotifyTaskDO; + +import java.util.List; + +/** + * 回调通知 Service 接口 + * + * @author 芋道源码 + */ +public interface PayNotifyService { + + /** + * 创建回调通知任务 + * + * @param type 类型 + * @param dataId 数据编号 + */ + void createPayNotifyTask(Integer type, Long dataId); + + /** + * 执行回调通知 + * + * 注意,该方法提供给定时任务调用。目前是 yudao-server 进行调用 + * @return 通知数量 + */ + int executeNotify() throws InterruptedException; + + /** + * 获得回调通知 + * + * @param id 编号 + * @return 回调通知 + */ + PayNotifyTaskDO getNotifyTask(Long id); + + /** + * 获得回调通知分页 + * + * @param pageReqVO 分页查询 + * @return 回调通知分页 + */ + PageResult getNotifyTaskPage(PayNotifyTaskPageReqVO pageReqVO); + + /** + * 获得回调日志列表 + * + * @param taskId 任务编号 + * @return 日志列表 + */ + List getNotifyLogList(Long taskId); + +} diff --git a/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/service/notify/PayNotifyServiceImpl.java b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/service/notify/PayNotifyServiceImpl.java new file mode 100644 index 0000000..0956087 --- /dev/null +++ b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/service/notify/PayNotifyServiceImpl.java @@ -0,0 +1,323 @@ +package cn.iocoder.yudao.module.pay.service.notify; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.exceptions.ExceptionUtil; +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.extra.spring.SpringUtil; +import cn.hutool.http.HttpResponse; +import cn.hutool.http.HttpUtil; +import cn.iocoder.yudao.framework.common.pojo.CommonResult; +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.common.util.date.DateUtils; +import cn.iocoder.yudao.framework.common.util.json.JsonUtils; +import cn.iocoder.yudao.framework.tenant.core.util.TenantUtils; +import cn.iocoder.yudao.module.pay.api.notify.dto.PayOrderNotifyReqDTO; +import cn.iocoder.yudao.module.pay.api.notify.dto.PayRefundNotifyReqDTO; +import cn.iocoder.yudao.module.pay.api.notify.dto.PayTransferNotifyReqDTO; +import cn.iocoder.yudao.module.pay.controller.admin.notify.vo.PayNotifyTaskPageReqVO; +import cn.iocoder.yudao.module.pay.dal.dataobject.notify.PayNotifyLogDO; +import cn.iocoder.yudao.module.pay.dal.dataobject.notify.PayNotifyTaskDO; +import cn.iocoder.yudao.module.pay.dal.dataobject.order.PayOrderDO; +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.mysql.notify.PayNotifyLogMapper; +import cn.iocoder.yudao.module.pay.dal.mysql.notify.PayNotifyTaskMapper; +import cn.iocoder.yudao.module.pay.dal.redis.notify.PayNotifyLockRedisDAO; +import cn.iocoder.yudao.module.pay.enums.notify.PayNotifyStatusEnum; +import cn.iocoder.yudao.module.pay.enums.notify.PayNotifyTypeEnum; +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 com.google.common.annotations.VisibleForTesting; +import lombok.extern.slf4j.Slf4j; +import org.springframework.context.annotation.Lazy; +import org.springframework.scheduling.annotation.Async; +import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.transaction.support.TransactionSynchronization; +import org.springframework.transaction.support.TransactionSynchronizationManager; + +import jakarta.annotation.Resource; +import jakarta.validation.Valid; +import java.time.Duration; +import java.time.LocalDateTime; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +import static cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.addTime; +import static cn.iocoder.yudao.module.pay.framework.job.config.PayJobConfiguration.NOTIFY_THREAD_POOL_TASK_EXECUTOR; + +/** + * 支付通知 Core Service 实现类 + * + * @author 芋道源码 + */ +@Service +@Valid +@Slf4j +public class PayNotifyServiceImpl implements PayNotifyService { + + /** + * 通知超时时间,单位:秒 + */ + public static final int NOTIFY_TIMEOUT = 120; + /** + * {@link #NOTIFY_TIMEOUT} 的毫秒 + */ + public static final long NOTIFY_TIMEOUT_MILLIS = 120 * DateUtils.SECOND_MILLIS; + + @Resource + @Lazy // 循环依赖,避免报错 + private PayOrderService orderService; + @Resource + @Lazy // 循环依赖,避免报错 + private PayRefundService refundService; + @Resource + @Lazy // 循环依赖,避免报错 + private PayTransferService transferService; + + @Resource + private PayNotifyTaskMapper notifyTaskMapper; + @Resource + private PayNotifyLogMapper notifyLogMapper; + + @Resource(name = NOTIFY_THREAD_POOL_TASK_EXECUTOR) + private ThreadPoolTaskExecutor threadPoolTaskExecutor; + + @Resource + private PayNotifyLockRedisDAO notifyLockCoreRedisDAO; + + @Override + @Transactional(rollbackFor = Exception.class) + public void createPayNotifyTask(Integer type, Long dataId) { + PayNotifyTaskDO task = new PayNotifyTaskDO().setType(type).setDataId(dataId); + task.setStatus(PayNotifyStatusEnum.WAITING.getStatus()).setNextNotifyTime(LocalDateTime.now()) + .setNotifyTimes(0).setMaxNotifyTimes(PayNotifyTaskDO.NOTIFY_FREQUENCY.length + 1); + // 补充 appId + notifyUrl + merchant* 字段 + if (Objects.equals(task.getType(), PayNotifyTypeEnum.ORDER.getType())) { + PayOrderDO order = orderService.getOrder(task.getDataId()); // 不进行非空判断,有问题直接异常 + task.setAppId(order.getAppId()).setNotifyUrl(order.getNotifyUrl()) + .setMerchantOrderId(order.getMerchantOrderId()); + } else if (Objects.equals(task.getType(), PayNotifyTypeEnum.REFUND.getType())) { + PayRefundDO refund = refundService.getRefund(task.getDataId()); + task.setAppId(refund.getAppId()).setNotifyUrl(refund.getNotifyUrl()) + .setMerchantOrderId(refund.getMerchantOrderId()).setMerchantRefundId(refund.getMerchantRefundId()); + } else if (Objects.equals(task.getType(), PayNotifyTypeEnum.TRANSFER.getType())) { + PayTransferDO transfer = transferService.getTransfer(task.getDataId()); + task.setAppId(transfer.getAppId()).setNotifyUrl(transfer.getNotifyUrl()) + .setMerchantTransferId(transfer.getMerchantTransferId()); + } + + // 执行插入 + notifyTaskMapper.insert(task); + + // 必须在事务提交后,在发起任务,否则 PayNotifyTaskDO 还没入库,就提前回调接入的业务 + TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() { + + @Override + public void afterCommit() { + // 异步的原因:避免阻塞当前事务,无需等待结果 + getSelf().executeNotifyAsync(task); + } + + }); + } + + @Override + public int executeNotify() throws InterruptedException { + // 获得需要通知的任务 + List tasks = notifyTaskMapper.selectListByNotify(); + if (CollUtil.isEmpty(tasks)) { + return 0; + } + + // 遍历,逐个通知 + CountDownLatch latch = new CountDownLatch(tasks.size()); + tasks.forEach(task -> threadPoolTaskExecutor.execute(() -> { + try { + executeNotify(task); + } finally { + latch.countDown(); + } + })); + // 等待完成 + awaitExecuteNotify(latch); + // 返回执行完成的任务数(成功 + 失败) + return tasks.size(); + } + + /** + * 等待全部支付通知的完成 + * 每 1 秒会打印一次剩余任务数量 + * + * @param latch Latch + * @throws InterruptedException 如果被打断 + */ + private void awaitExecuteNotify(CountDownLatch latch) throws InterruptedException { + long size = latch.getCount(); + for (int i = 0; i < NOTIFY_TIMEOUT; i++) { + if (latch.await(1L, TimeUnit.SECONDS)) { + return; + } + log.info("[awaitExecuteNotify][任务处理中, 总任务数({}) 剩余任务数({})]", size, latch.getCount()); + } + log.error("[awaitExecuteNotify][任务未处理完,总任务数({}) 剩余任务数({})]", size, latch.getCount()); + } + + /** + * 异步执行单个支付通知 + * + * @param task 通知任务 + */ + @Async + public void executeNotifyAsync(PayNotifyTaskDO task) { + executeNotify(task); + } + + /** + * 【加锁】执行单个支付通知 + * + * @param task 通知任务 + */ + public void executeNotify(PayNotifyTaskDO task) { + // 分布式锁,避免并发问题 + notifyLockCoreRedisDAO.lock(task.getId(), NOTIFY_TIMEOUT_MILLIS, () -> { + // 校验,当前任务是否已经被通知过 + // 虽然已经通过分布式加锁,但是可能同时满足通知的条件,然后都去获得锁。此时,第一个执行完后,第二个还是能拿到锁,然后会再执行一次。 + // 因此,此处我们通过第 notifyTimes 通知次数是否匹配来判断 + PayNotifyTaskDO dbTask = notifyTaskMapper.selectById(task.getId()); + if (ObjectUtil.notEqual(task.getNotifyTimes(), dbTask.getNotifyTimes())) { + log.warn("[executeNotifySync][task({}) 任务被忽略,原因是它的通知不是第 ({}) 次,可能是因为并发执行了]", + JsonUtils.toJsonString(task), dbTask.getNotifyTimes()); + return; + } + + // 执行通知 + getSelf().executeNotify0(dbTask); + }); + } + + @Transactional(rollbackFor = Exception.class) + public void executeNotify0(PayNotifyTaskDO task) { + // 发起回调 + CommonResult invokeResult = null; + Throwable invokeException = null; + try { + invokeResult = executeNotifyInvoke(task); + } catch (Throwable e) { + invokeException = e; + } + + // 处理结果 + Integer newStatus = processNotifyResult(task, invokeResult, invokeException); + + // 记录 PayNotifyLog 日志 + String response = invokeException != null ? ExceptionUtil.getRootCauseMessage(invokeException) : + JsonUtils.toJsonString(invokeResult); + notifyLogMapper.insert(PayNotifyLogDO.builder().taskId(task.getId()) + .notifyTimes(task.getNotifyTimes() + 1).status(newStatus).response(response).build()); + } + + /** + * 执行单个支付任务的 HTTP 调用 + * + * @param task 通知任务 + * @return HTTP 响应 + */ + private CommonResult executeNotifyInvoke(PayNotifyTaskDO task) { + // 拼接 body 参数 + Object request; + if (Objects.equals(task.getType(), PayNotifyTypeEnum.ORDER.getType())) { + request = PayOrderNotifyReqDTO.builder().merchantOrderId(task.getMerchantOrderId()) + .payOrderId(task.getDataId()).build(); + } else if (Objects.equals(task.getType(), PayNotifyTypeEnum.REFUND.getType())) { + request = PayRefundNotifyReqDTO.builder().merchantOrderId(task.getMerchantOrderId()) + .merchantRefundId(task.getMerchantRefundId()) + .payRefundId(task.getDataId()).build(); + } else if (Objects.equals(task.getType(), PayNotifyTypeEnum.TRANSFER.getType())) { + request = PayTransferNotifyReqDTO.builder().merchantTransferId(task.getMerchantTransferId()) + .payTransferId(task.getDataId()).build(); + } else { + throw new RuntimeException("未知的通知任务类型:" + JsonUtils.toJsonString(task)); + } + // 拼接 header 参数 + Map headers = new HashMap<>(); + TenantUtils.addTenantHeader(headers, task.getTenantId()); + + // 发起请求 + try (HttpResponse response = HttpUtil.createPost(task.getNotifyUrl()) + .body(JsonUtils.toJsonString(request)).addHeaders(headers) + .timeout((int) NOTIFY_TIMEOUT_MILLIS).execute()) { + // 解析结果 + return JsonUtils.parseObject(response.body(), CommonResult.class); + } + } + + /** + * 处理并更新通知结果 + * + * @param task 通知任务 + * @param invokeResult 通知结果 + * @param invokeException 通知异常 + * @return 最终任务的状态 + */ + @VisibleForTesting + Integer processNotifyResult(PayNotifyTaskDO task, CommonResult invokeResult, Throwable invokeException) { + // 设置通用的更新 PayNotifyTaskDO 的字段 + PayNotifyTaskDO updateTask = new PayNotifyTaskDO() + .setId(task.getId()) + .setLastExecuteTime(LocalDateTime.now()) + .setNotifyTimes(task.getNotifyTimes() + 1); + + // 情况一:调用成功 + if (invokeResult != null && invokeResult.isSuccess()) { + updateTask.setStatus(PayNotifyStatusEnum.SUCCESS.getStatus()); + notifyTaskMapper.updateById(updateTask); + return updateTask.getStatus(); + } + + // 情况二:调用失败、调用异常 + // 2.1 超过最大回调次数 + if (updateTask.getNotifyTimes() >= PayNotifyTaskDO.NOTIFY_FREQUENCY.length) { + updateTask.setStatus(PayNotifyStatusEnum.FAILURE.getStatus()); + notifyTaskMapper.updateById(updateTask); + return updateTask.getStatus(); + } + // 2.2 未超过最大回调次数 + updateTask.setNextNotifyTime(addTime(Duration.ofSeconds(PayNotifyTaskDO.NOTIFY_FREQUENCY[updateTask.getNotifyTimes()]))); + updateTask.setStatus(invokeException != null ? PayNotifyStatusEnum.REQUEST_FAILURE.getStatus() + : PayNotifyStatusEnum.REQUEST_SUCCESS.getStatus()); + notifyTaskMapper.updateById(updateTask); + return updateTask.getStatus(); + } + + @Override + public PayNotifyTaskDO getNotifyTask(Long id) { + return notifyTaskMapper.selectById(id); + } + + @Override + public PageResult getNotifyTaskPage(PayNotifyTaskPageReqVO pageReqVO) { + return notifyTaskMapper.selectPage(pageReqVO); + } + + @Override + public List getNotifyLogList(Long taskId) { + return notifyLogMapper.selectListByTaskId(taskId); + } + + /** + * 获得自身的代理对象,解决 AOP 生效问题 + * + * @return 自己 + */ + private PayNotifyServiceImpl getSelf() { + return SpringUtil.getBean(getClass()); + } + +} diff --git a/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/service/order/PayOrderService.java b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/service/order/PayOrderService.java new file mode 100755 index 0000000..c4496f8 --- /dev/null +++ b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/service/order/PayOrderService.java @@ -0,0 +1,159 @@ +package cn.iocoder.yudao.module.pay.service.order; + +import cn.iocoder.yudao.framework.common.pojo.PageResult; +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; +import cn.iocoder.yudao.module.pay.controller.admin.order.vo.PayOrderSubmitReqVO; +import cn.iocoder.yudao.module.pay.controller.admin.order.vo.PayOrderSubmitRespVO; +import cn.iocoder.yudao.module.pay.dal.dataobject.order.PayOrderDO; +import cn.iocoder.yudao.module.pay.dal.dataobject.order.PayOrderExtensionDO; + +import jakarta.validation.Valid; +import jakarta.validation.constraints.NotEmpty; +import java.time.LocalDateTime; +import java.util.Collection; +import java.util.List; + +/** + * 支付订单 Service 接口 + * + * @author aquan + */ +public interface PayOrderService { + + /** + * 获得支付订单 + * + * @param id 编号 + * @return 支付订单 + */ + PayOrderDO getOrder(Long id); + + /** + * 获得支付订单 + * + * @param appId 应用编号 + * @param merchantOrderId 商户订单编号 + * @return 支付订单 + */ + PayOrderDO getOrder(Long appId, String merchantOrderId); + + /** + * 获得支付订单列表 + * + * @param ids 编号数组 + * @return 支付订单列表 + */ + List getOrderList(Collection ids); + + /** + * 获得指定应用的订单数量 + * + * @param appId 应用编号 + * @return 订单数量 + */ + Long getOrderCountByAppId(Long appId); + + /** + * 获得支付订单分页 + * + * @param pageReqVO 分页查询 + * @return 支付订单分页 + */ + PageResult getOrderPage(PayOrderPageReqVO pageReqVO); + + /** + * 获得支付订单列表, 用于 Excel 导出 + * + * @param exportReqVO 查询条件 + * @return 支付订单列表 + */ + List getOrderList(PayOrderExportReqVO exportReqVO); + + /** + * 创建支付单 + * + * @param reqDTO 创建请求 + * @return 支付单编号 + */ + Long createOrder(@Valid PayOrderCreateReqDTO reqDTO); + + /** + * 提交支付 + * 此时,会发起支付渠道的调用 + * + * @param reqVO 提交请求 + * @param userIp 提交 IP + * @return 提交结果 + */ + PayOrderSubmitRespVO submitOrder(@Valid PayOrderSubmitReqVO reqVO, + @NotEmpty(message = "提交 IP 不能为空") String userIp); + + /** + * 通知支付单成功 + * + * @param channelId 渠道编号 + * @param notify 通知 + */ + void notifyOrder(Long channelId, PayOrderRespDTO notify); + + /** + * 更新支付订单的退款金额 + * + * @param id 编号 + * @param incrRefundPrice 增加的退款金额 + */ + void updateOrderRefundPrice(Long id, Integer incrRefundPrice); + + /** + * 更新支付订单价格 + * + * @param id 支付单编号 + * @param payPrice 支付单价格 + */ + void updatePayOrderPrice(Long id, Integer payPrice); + + /** + * 获得支付订单 + * + * @param id 编号 + * @return 支付订单 + */ + PayOrderExtensionDO getOrderExtension(Long id); + + /** + * 获得支付订单 + * + * @param no 支付订单 no + * @return 支付订单 + */ + PayOrderExtensionDO getOrderExtensionByNo(String no); + + /** + * 同步订单的支付状态 + * + * @param minCreateTime 最小创建时间 + * @return 同步到已支付的订单数量 + */ + int syncOrder(LocalDateTime minCreateTime); + + /** + * 同步订单的支付状态 + * + * 1. Quietly 表示,即使同步失败,也不会抛出异常 + * 2. 什么时候回出现异常?因为是主动同步,可能和支付渠道的异步回调存在并发冲突,导致抛出异常 + * + * @param id 订单编号 + */ + void syncOrderQuietly(Long id); + + /** + * 将已过期的订单,状态修改为已关闭 + * + * @return 过期的订单数量 + */ + int expireOrder(); + +} diff --git a/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/service/order/PayOrderServiceImpl.java b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/service/order/PayOrderServiceImpl.java new file mode 100755 index 0000000..f468355 --- /dev/null +++ b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/service/order/PayOrderServiceImpl.java @@ -0,0 +1,605 @@ +package cn.iocoder.yudao.module.pay.service.order; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.util.StrUtil; +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.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; +import cn.iocoder.yudao.module.pay.controller.admin.order.vo.PayOrderPageReqVO; +import cn.iocoder.yudao.module.pay.controller.admin.order.vo.PayOrderSubmitReqVO; +import cn.iocoder.yudao.module.pay.controller.admin.order.vo.PayOrderSubmitRespVO; +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.channel.PayChannelDO; +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.mysql.order.PayOrderExtensionMapper; +import cn.iocoder.yudao.module.pay.dal.mysql.order.PayOrderMapper; +import cn.iocoder.yudao.module.pay.dal.redis.no.PayNoRedisDAO; +import cn.iocoder.yudao.module.pay.enums.notify.PayNotifyTypeEnum; +import cn.iocoder.yudao.module.pay.enums.order.PayOrderStatusEnum; +import cn.iocoder.yudao.module.pay.framework.pay.config.PayProperties; +import cn.iocoder.yudao.module.pay.service.app.PayAppService; +import cn.iocoder.yudao.module.pay.service.channel.PayChannelService; +import cn.iocoder.yudao.module.pay.service.notify.PayNotifyService; +import com.google.common.annotations.VisibleForTesting; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.validation.annotation.Validated; + +import jakarta.annotation.Resource; +import java.time.LocalDateTime; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Objects; + +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; +import static cn.iocoder.yudao.framework.common.util.json.JsonUtils.toJsonString; +import static cn.iocoder.yudao.module.pay.enums.ErrorCodeConstants.*; + +/** + * 支付订单 Service 实现类 + * + * @author aquan + */ +@Service +@Validated +@Slf4j +public class PayOrderServiceImpl implements PayOrderService { + + @Resource + private PayProperties payProperties; + + @Resource + private PayOrderMapper orderMapper; + @Resource + private PayOrderExtensionMapper orderExtensionMapper; + @Resource + private PayNoRedisDAO noRedisDAO; + + @Resource + private PayAppService appService; + @Resource + private PayChannelService channelService; + @Resource + private PayNotifyService notifyService; + + @Override + public PayOrderDO getOrder(Long id) { + return orderMapper.selectById(id); + } + + @Override + public PayOrderDO getOrder(Long appId, String merchantOrderId) { + return orderMapper.selectByAppIdAndMerchantOrderId(appId, merchantOrderId); + } + + @Override + public List getOrderList(Collection ids) { + if (CollUtil.isEmpty(ids)) { + return Collections.emptyList(); + } + return orderMapper.selectBatchIds(ids); + } + + @Override + public Long getOrderCountByAppId(Long appId) { + return orderMapper.selectCountByAppId(appId); + } + + @Override + public PageResult getOrderPage(PayOrderPageReqVO pageReqVO) { + return orderMapper.selectPage(pageReqVO); + } + + @Override + public List getOrderList(PayOrderExportReqVO exportReqVO) { + return orderMapper.selectList(exportReqVO); + } + + @Override + public Long createOrder(PayOrderCreateReqDTO reqDTO) { + // 校验 App + PayAppDO app = appService.validPayApp(reqDTO.getAppKey()); + + // 查询对应的支付交易单是否已经存在。如果是,则直接返回 + PayOrderDO order = orderMapper.selectByAppIdAndMerchantOrderId( + app.getId(), reqDTO.getMerchantOrderId()); + if (order != null) { + log.warn("[createOrder][appId({}) merchantOrderId({}) 已经存在对应的支付单({})]", order.getAppId(), + order.getMerchantOrderId(), toJsonString(order)); // 理论来说,不会出现这个情况 + return order.getId(); + } + + // 创建支付交易单 + order = PayOrderConvert.INSTANCE.convert(reqDTO).setAppId(app.getId()) + // 商户相关字段 + .setNotifyUrl(app.getOrderNotifyUrl()) + // 订单相关字段 + .setStatus(PayOrderStatusEnum.WAITING.getStatus()) + // 退款相关字段 + .setRefundPrice(0); + orderMapper.insert(order); + return order.getId(); + } + + @Override // 注意,这里不能添加事务注解,避免调用支付渠道失败时,将 PayOrderExtensionDO 回滚了 + public PayOrderSubmitRespVO submitOrder(PayOrderSubmitReqVO reqVO, String userIp) { + // 1.1 获得 PayOrderDO ,并校验其是否存在 + PayOrderDO order = validateOrderCanSubmit(reqVO.getId()); + // 1.32 校验支付渠道是否有效 + PayChannelDO channel = validateChannelCanSubmit(order.getAppId(), reqVO.getChannelCode()); + PayClient client = channelService.getPayClient(channel.getId()); + + // 2. 插入 PayOrderExtensionDO + String no = noRedisDAO.generate(payProperties.getOrderNoPrefix()); + PayOrderExtensionDO orderExtension = PayOrderConvert.INSTANCE.convert(reqVO, userIp) + .setOrderId(order.getId()).setNo(no) + .setChannelId(channel.getId()).setChannelCode(channel.getCode()) + .setStatus(PayOrderStatusEnum.WAITING.getStatus()); + orderExtensionMapper.insert(orderExtension); + + // 3. 调用三方接口 + PayOrderUnifiedReqDTO unifiedOrderReqDTO = PayOrderConvert.INSTANCE.convert2(reqVO, userIp) + // 商户相关的字段 + .setOutTradeNo(orderExtension.getNo()) // 注意,此处使用的是 PayOrderExtensionDO.no 属性! + .setSubject(order.getSubject()).setBody(order.getBody()) + .setNotifyUrl(genChannelOrderNotifyUrl(channel)) + .setReturnUrl(reqVO.getReturnUrl()) + // 订单相关字段 + .setPrice(order.getPrice()).setExpireTime(order.getExpireTime()); + PayOrderRespDTO unifiedOrderResp = client.unifiedOrder(unifiedOrderReqDTO); + + // 4. 如果调用直接支付成功,则直接更新支付单状态为成功。例如说:付款码支付,免密支付时,就直接验证支付成功 + if (unifiedOrderResp != null) { + try { + getSelf().notifyOrder(channel, unifiedOrderResp); + } catch (Exception e) { + // 兼容 https://gitee.com/zhijiantianya/yudao-cloud/issues/I8SM9H 场景 + // 支付宝或微信扫码之后时,由于 PayClient 是直接返回支付成功,而支付也会有回调,导致存在并发更新问题,此时一般是可以 try catch 直接忽略 + log.warn("[submitOrder][order({}) channel({}) 支付结果({}) 通知时发生异常,可能是并发问题]", + order, channel, unifiedOrderResp, e); + } + // 如有渠道错误码,则抛出业务异常,提示用户 + if (StrUtil.isNotEmpty(unifiedOrderResp.getChannelErrorCode())) { + throw exception(PAY_ORDER_SUBMIT_CHANNEL_ERROR, unifiedOrderResp.getChannelErrorCode(), + unifiedOrderResp.getChannelErrorMsg()); + } + // 此处需要读取最新的状态 + order = orderMapper.selectById(order.getId()); + } + return PayOrderConvert.INSTANCE.convert(order, unifiedOrderResp); + } + + private PayOrderDO validateOrderCanSubmit(Long id) { + PayOrderDO order = orderMapper.selectById(id); + if (order == null) { // 是否存在 + throw exception(PAY_ORDER_NOT_FOUND); + } + if (PayOrderStatusEnum.isSuccess(order.getStatus())) { // 校验状态,发现已支付 + throw exception(PAY_ORDER_STATUS_IS_SUCCESS); + } + if (!PayOrderStatusEnum.WAITING.getStatus().equals(order.getStatus())) { // 校验状态,必须是待支付 + throw exception(PAY_ORDER_STATUS_IS_NOT_WAITING); + } + if (LocalDateTimeUtils.beforeNow(order.getExpireTime())) { // 校验是否过期 + throw exception(PAY_ORDER_IS_EXPIRED); + } + + // 【重要】校验是否支付拓展单已支付,只是没有回调、或者数据不正常 + validateOrderActuallyPaid(id); + return order; + } + + /** + * 校验支付订单实际已支付 + * + * @param id 支付编号 + */ + @VisibleForTesting + void validateOrderActuallyPaid(Long id) { + List orderExtensions = orderExtensionMapper.selectListByOrderId(id); + orderExtensions.forEach(orderExtension -> { + // 情况一:校验数据库中的 orderExtension 是不是已支付 + if (PayOrderStatusEnum.isSuccess(orderExtension.getStatus())) { + log.warn("[validateOrderCanSubmit][order({}) 的 extension({}) 已支付,可能是数据不一致]", + id, orderExtension.getId()); + throw exception(PAY_ORDER_EXTENSION_IS_PAID); + } + // 情况二:调用三方接口,查询支付单状态,是不是已支付 + PayClient payClient = channelService.getPayClient(orderExtension.getChannelId()); + if (payClient == null) { + log.error("[validateOrderCanSubmit][渠道编号({}) 找不到对应的支付客户端]", orderExtension.getChannelId()); + return; + } + PayOrderRespDTO respDTO = payClient.getOrder(orderExtension.getNo()); + if (respDTO != null && PayOrderStatusEnum.isSuccess(respDTO.getStatus())) { + log.warn("[validateOrderCanSubmit][order({}) 的 PayOrderRespDTO({}) 已支付,可能是回调延迟]", + id, toJsonString(respDTO)); + throw exception(PAY_ORDER_EXTENSION_IS_PAID); + } + }); + } + + private PayChannelDO validateChannelCanSubmit(Long appId, String channelCode) { + // 校验 App + appService.validPayApp(appId); + // 校验支付渠道是否有效 + PayChannelDO channel = channelService.validPayChannel(appId, channelCode); + PayClient client = channelService.getPayClient(channel.getId()); + if (client == null) { + log.error("[validatePayChannelCanSubmit][渠道编号({}) 找不到对应的支付客户端]", channel.getId()); + throw exception(CHANNEL_NOT_FOUND); + } + return channel; + } + + /** + * 根据支付渠道的编码,生成支付渠道的回调地址 + * + * @param channel 支付渠道 + * @return 支付渠道的回调地址 配置地址 + "/" + channel id + */ + private String genChannelOrderNotifyUrl(PayChannelDO channel) { + return payProperties.getOrderNotifyUrl() + "/" + channel.getId(); + } + + @Override + public void notifyOrder(Long channelId, PayOrderRespDTO notify) { + // 校验支付渠道是否有效 + PayChannelDO channel = channelService.validPayChannel(channelId); + // 更新支付订单为已支付 + TenantUtils.execute(channel.getTenantId(), () -> getSelf().notifyOrder(channel, notify)); + } + + /** + * 通知并更新订单的支付结果 + * + * @param channel 支付渠道 + * @param notify 通知 + */ + @Transactional(rollbackFor = Exception.class) + // 注意,如果是方法内调用该方法,需要通过 getSelf().notifyPayOrder(channel, notify) 调用,否则事务不生效 + public void notifyOrder(PayChannelDO channel, PayOrderRespDTO notify) { + // 情况一:支付成功的回调 + if (PayOrderStatusEnum.isSuccess(notify.getStatus())) { + notifyOrderSuccess(channel, notify); + return; + } + // 情况二:支付失败的回调 + if (PayOrderStatusEnum.isClosed(notify.getStatus())) { + notifyOrderClosed(channel, notify); + } + // 情况三:WAITING:无需处理 + // 情况四:REFUND:通过退款回调处理 + } + + private void notifyOrderSuccess(PayChannelDO channel, PayOrderRespDTO notify) { + // 1. 更新 PayOrderExtensionDO 支付成功 + PayOrderExtensionDO orderExtension = updateOrderSuccess(notify); + // 2. 更新 PayOrderDO 支付成功 + Boolean paid = updateOrderSuccess(channel, orderExtension, notify); + if (paid) { // 如果之前已经成功回调,则直接返回,不用重复记录支付通知记录;例如说:支付平台重复回调 + return; + } + + // 3. 插入支付通知记录 + notifyService.createPayNotifyTask(PayNotifyTypeEnum.ORDER.getType(), + orderExtension.getOrderId()); + } + + /** + * 更新 PayOrderExtensionDO 支付成功 + * + * @param notify 通知 + * @return PayOrderExtensionDO 对象 + */ + private PayOrderExtensionDO updateOrderSuccess(PayOrderRespDTO notify) { + // 1. 查询 PayOrderExtensionDO + PayOrderExtensionDO orderExtension = orderExtensionMapper.selectByNo(notify.getOutTradeNo()); + if (orderExtension == null) { + throw exception(PAY_ORDER_EXTENSION_NOT_FOUND); + } + if (PayOrderStatusEnum.isSuccess(orderExtension.getStatus())) { // 如果已经是成功,直接返回,不用重复更新 + log.info("[updateOrderExtensionSuccess][orderExtension({}) 已经是已支付,无需更新]", orderExtension.getId()); + return orderExtension; + } + if (ObjectUtil.notEqual(orderExtension.getStatus(), PayOrderStatusEnum.WAITING.getStatus())) { // 校验状态,必须是待支付 + throw exception(PAY_ORDER_EXTENSION_STATUS_IS_NOT_WAITING); + } + + // 2. 更新 PayOrderExtensionDO + int updateCounts = orderExtensionMapper.updateByIdAndStatus(orderExtension.getId(), orderExtension.getStatus(), + PayOrderExtensionDO.builder().status(PayOrderStatusEnum.SUCCESS.getStatus()).channelNotifyData(toJsonString(notify)).build()); + if (updateCounts == 0) { // 校验状态,必须是待支付 + throw exception(PAY_ORDER_EXTENSION_STATUS_IS_NOT_WAITING); + } + log.info("[updateOrderExtensionSuccess][orderExtension({}) 更新为已支付]", orderExtension.getId()); + return orderExtension; + } + + /** + * 更新 PayOrderDO 支付成功 + * + * @param channel 支付渠道 + * @param orderExtension 支付拓展单 + * @param notify 通知回调 + * @return 是否之前已经成功回调 + */ + private Boolean updateOrderSuccess(PayChannelDO channel, PayOrderExtensionDO orderExtension, + PayOrderRespDTO notify) { + // 1. 判断 PayOrderDO 是否处于待支付 + PayOrderDO order = orderMapper.selectById(orderExtension.getOrderId()); + if (order == null) { + throw exception(PAY_ORDER_NOT_FOUND); + } + if (PayOrderStatusEnum.isSuccess(order.getStatus()) // 如果已经是成功,直接返回,不用重复更新 + && Objects.equals(order.getExtensionId(), orderExtension.getId())) { + log.info("[updateOrderExtensionSuccess][order({}) 已经是已支付,无需更新]", order.getId()); + return true; + } + if (!PayOrderStatusEnum.WAITING.getStatus().equals(order.getStatus())) { // 校验状态,必须是待支付 + throw exception(PAY_ORDER_STATUS_IS_NOT_WAITING); + } + + // 2. 更新 PayOrderDO + int updateCounts = orderMapper.updateByIdAndStatus(order.getId(), PayOrderStatusEnum.WAITING.getStatus(), + PayOrderDO.builder().status(PayOrderStatusEnum.SUCCESS.getStatus()) + .channelId(channel.getId()).channelCode(channel.getCode()) + .successTime(notify.getSuccessTime()).extensionId(orderExtension.getId()).no(orderExtension.getNo()) + .channelOrderNo(notify.getChannelOrderNo()).channelUserId(notify.getChannelUserId()) + .channelFeeRate(channel.getFeeRate()) + .channelFeePrice(MoneyUtils.calculateRatePrice(order.getPrice(), channel.getFeeRate())) + .build()); + if (updateCounts == 0) { // 校验状态,必须是待支付 + throw exception(PAY_ORDER_STATUS_IS_NOT_WAITING); + } + log.info("[updateOrderExtensionSuccess][order({}) 更新为已支付]", order.getId()); + return false; + } + + private void notifyOrderClosed(PayChannelDO channel, PayOrderRespDTO notify) { + updateOrderExtensionClosed(channel, notify); + } + + @SuppressWarnings("unused") + private void updateOrderExtensionClosed(PayChannelDO channel, PayOrderRespDTO notify) { + // 1. 查询 PayOrderExtensionDO + PayOrderExtensionDO orderExtension = orderExtensionMapper.selectByNo(notify.getOutTradeNo()); + if (orderExtension == null) { + throw exception(PAY_ORDER_EXTENSION_NOT_FOUND); + } + if (PayOrderStatusEnum.isClosed(orderExtension.getStatus())) { // 如果已经是关闭,直接返回,不用重复更新 + log.info("[updateOrderExtensionClosed][orderExtension({}) 已经是支付关闭,无需更新]", orderExtension.getId()); + return; + } + // 一般出现先是支付成功,然后支付关闭,都是全部退款导致关闭的场景。这个情况,我们不更新支付拓展单,只通过退款流程,更新支付单 + if (PayOrderStatusEnum.isSuccess(orderExtension.getStatus())) { + log.info("[updateOrderExtensionClosed][orderExtension({}) 是已支付,无需更新为支付关闭]", orderExtension.getId()); + return; + } + if (ObjectUtil.notEqual(orderExtension.getStatus(), PayOrderStatusEnum.WAITING.getStatus())) { // 校验状态,必须是待支付 + throw exception(PAY_ORDER_EXTENSION_STATUS_IS_NOT_WAITING); + } + + // 2. 更新 PayOrderExtensionDO + int updateCounts = orderExtensionMapper.updateByIdAndStatus(orderExtension.getId(), orderExtension.getStatus(), + PayOrderExtensionDO.builder().status(PayOrderStatusEnum.CLOSED.getStatus()).channelNotifyData(toJsonString(notify)) + .channelErrorCode(notify.getChannelErrorCode()).channelErrorMsg(notify.getChannelErrorMsg()).build()); + if (updateCounts == 0) { // 校验状态,必须是待支付 + throw exception(PAY_ORDER_EXTENSION_STATUS_IS_NOT_WAITING); + } + log.info("[updateOrderExtensionClosed][orderExtension({}) 更新为支付关闭]", orderExtension.getId()); + } + + @Override + public void updateOrderRefundPrice(Long id, Integer incrRefundPrice) { + PayOrderDO order = orderMapper.selectById(id); + if (order == null) { + throw exception(PAY_ORDER_NOT_FOUND); + } + if (!PayOrderStatusEnum.isSuccessOrRefund(order.getStatus())) { + throw exception(PAY_ORDER_REFUND_FAIL_STATUS_ERROR); + } + if (order.getRefundPrice() + incrRefundPrice > order.getPrice()) { + throw exception(REFUND_PRICE_EXCEED); + } + + // 更新订单 + PayOrderDO updateObj = new PayOrderDO() + .setRefundPrice(order.getRefundPrice() + incrRefundPrice) + .setStatus(PayOrderStatusEnum.REFUND.getStatus()); + int updateCount = orderMapper.updateByIdAndStatus(id, order.getStatus(), updateObj); + if (updateCount == 0) { + throw exception(PAY_ORDER_REFUND_FAIL_STATUS_ERROR); + } + } + + @Override + public void updatePayOrderPrice(Long id, Integer payPrice) { + PayOrderDO order = orderMapper.selectById(id); + if (order == null) { + throw exception(PAY_ORDER_NOT_FOUND); + } + if (ObjectUtil.notEqual(PayOrderStatusEnum.WAITING.getStatus(), order.getStatus())) { + throw exception(PAY_ORDER_STATUS_IS_NOT_WAITING); + } + if (ObjectUtil.equal(order.getPrice(), payPrice)) { + return; + } + + orderMapper.updateById(new PayOrderDO().setId(order.getId()).setPrice(payPrice)); + } + + @Override + public PayOrderExtensionDO getOrderExtension(Long id) { + return orderExtensionMapper.selectById(id); + } + + @Override + public PayOrderExtensionDO getOrderExtensionByNo(String no) { + return orderExtensionMapper.selectByNo(no); + } + + @Override + public int syncOrder(LocalDateTime minCreateTime) { + // 1. 查询指定创建时间前的待支付订单 + List orderExtensions = orderExtensionMapper.selectListByStatusAndCreateTimeGe( + PayOrderStatusEnum.WAITING.getStatus(), minCreateTime); + if (CollUtil.isEmpty(orderExtensions)) { + return 0; + } + // 2. 遍历执行 + int count = 0; + for (PayOrderExtensionDO orderExtension : orderExtensions) { + count += syncOrder(orderExtension) ? 1 : 0; + } + return count; + } + + @Override + public void syncOrderQuietly(Long id) { + // 1. 查询待支付订单 + List orderExtensions = orderExtensionMapper.selectListByOrderIdAndStatus(id, + PayOrderStatusEnum.WAITING.getStatus()); + + // 2. 遍历执行 + for (PayOrderExtensionDO orderExtension : orderExtensions) { + syncOrder(orderExtension); + } + } + + /** + * 同步单个支付拓展单 + * + * @param orderExtension 支付拓展单 + * @return 是否已支付 + */ + private boolean syncOrder(PayOrderExtensionDO orderExtension) { + try { + // 1.1 查询支付订单信息 + PayClient payClient = channelService.getPayClient(orderExtension.getChannelId()); + if (payClient == null) { + log.error("[syncOrder][渠道编号({}) 找不到对应的支付客户端]", orderExtension.getChannelId()); + return false; + } + PayOrderRespDTO respDTO = payClient.getOrder(orderExtension.getNo()); + // 如果查询到订单不存在,PayClient 返回的状态为关闭。但此时不能关闭订单。存在以下一种场景: + // 拉起渠道支付后,短时间内用户未及时完成支付,但是该订单同步定时任务恰巧自动触发了,主动查询结果为订单不存在。 + // 当用户支付成功之后,该订单状态在渠道的回调中无法从已关闭改为已支付,造成重大影响。 + // 考虑此定时任务是异常场景的兜底操作,因此这里不做变更,优先以回调为准。 + // 让订单自动随着支付渠道那边一起等到过期,确保渠道先过期关闭支付入口,而后通过订单过期定时任务关闭自己的订单。 + if (PayOrderStatusEnum.isClosed(respDTO.getStatus())) { + return false; + } + // 1.2 回调支付结果 + notifyOrder(orderExtension.getChannelId(), respDTO); + + // 2. 如果是已支付,则返回 true + return PayOrderStatusEnum.isSuccess(respDTO.getStatus()); + } catch (Throwable e) { + log.error("[syncOrder][orderExtension({}) 同步支付状态异常]", orderExtension.getId(), e); + return false; + } + } + + @Override + public int expireOrder() { + // 1. 查询过期的待支付订单 + List orders = orderMapper.selectListByStatusAndExpireTimeLt( + PayOrderStatusEnum.WAITING.getStatus(), LocalDateTime.now()); + if (CollUtil.isEmpty(orders)) { + return 0; + } + + // 2. 遍历执行 + int count = 0; + for (PayOrderDO order : orders) { + count += expireOrder(order) ? 1 : 0; + } + return count; + } + + /** + * 同步单个支付单 + * + * @param order 支付单 + * @return 是否已过期 + */ + private boolean expireOrder(PayOrderDO order) { + try { + // 1. 需要先处理关联的支付拓展单,避免错误的过期已支付 or 已退款的订单 + List orderExtensions = orderExtensionMapper.selectListByOrderId(order.getId()); + for (PayOrderExtensionDO orderExtension : orderExtensions) { + if (PayOrderStatusEnum.isClosed(orderExtension.getStatus())) { + continue; + } + // 情况一:校验数据库中的 orderExtension 是不是已支付 + if (PayOrderStatusEnum.isSuccess(orderExtension.getStatus())) { + log.error("[expireOrder][order({}) 的 extension({}) 已支付,可能是数据不一致]", + order.getId(), orderExtension.getId()); + return false; + } + // 情况二:调用三方接口,查询支付单状态,是不是已支付/已退款 + PayClient payClient = channelService.getPayClient(orderExtension.getChannelId()); + if (payClient == null) { + log.error("[expireOrder][渠道编号({}) 找不到对应的支付客户端]", orderExtension.getChannelId()); + return false; + } + PayOrderRespDTO respDTO = payClient.getOrder(orderExtension.getNo()); + 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 (PayOrderStatusEnum.isSuccess(respDTO.getStatus())) { + notifyOrder(orderExtension.getChannelId(), respDTO); + return false; + } + // 兜底逻辑:将支付拓展单更新为已关闭 + PayOrderExtensionDO updateObj = new PayOrderExtensionDO().setStatus(PayOrderStatusEnum.CLOSED.getStatus()) + .setChannelNotifyData(toJsonString(respDTO)); + if (orderExtensionMapper.updateByIdAndStatus(orderExtension.getId(), PayOrderStatusEnum.WAITING.getStatus(), + updateObj) == 0) { + log.error("[expireOrder][extension({}) 更新为支付关闭失败]", orderExtension.getId()); + return false; + } + log.info("[expireOrder][extension({}) 更新为支付关闭成功]", orderExtension.getId()); + } + + // 2. 都没有上述情况,可以安心更新为已关闭 + PayOrderDO updateObj = new PayOrderDO().setStatus(PayOrderStatusEnum.CLOSED.getStatus()); + if (orderMapper.updateByIdAndStatus(order.getId(), order.getStatus(), updateObj) == 0) { + log.error("[expireOrder][order({}) 更新为支付关闭失败]", order.getId()); + return false; + } + log.info("[expireOrder][order({}) 更新为支付关闭失败]", order.getId()); + return true; + } catch (Throwable e) { + log.error("[expireOrder][order({}) 过期订单异常]", order.getId(), e); + return false; + } + } + + /** + * 获得自身的代理对象,解决 AOP 生效问题 + * + * @return 自己 + */ + private PayOrderServiceImpl getSelf() { + return SpringUtil.getBean(getClass()); + } + +} diff --git a/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/service/refund/PayRefundService.java b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/service/refund/PayRefundService.java new file mode 100755 index 0000000..38b6e15 --- /dev/null +++ b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/service/refund/PayRefundService.java @@ -0,0 +1,82 @@ +package cn.iocoder.yudao.module.pay.service.refund; + +import cn.iocoder.yudao.framework.common.pojo.PageResult; +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; +import cn.iocoder.yudao.module.pay.dal.dataobject.refund.PayRefundDO; + +import java.util.List; + +/** + * 退款订单 Service 接口 + * + * @author aquan + */ +public interface PayRefundService { + + /** + * 获得退款订单 + * + * @param id 编号 + * @return 退款订单 + */ + PayRefundDO getRefund(Long id); + + /** + * 获得退款订单 + * + * @param no 外部退款单号 + * @return 退款订单 + */ + PayRefundDO getRefundByNo(String no); + + /** + * 获得指定应用的退款数量 + * + * @param appId 应用编号 + * @return 退款数量 + */ + Long getRefundCountByAppId(Long appId); + + /** + * 获得退款订单分页 + * + * @param pageReqVO 分页查询 + * @return 退款订单分页 + */ + PageResult getRefundPage(PayRefundPageReqVO pageReqVO); + + /** + * 获得退款订单列表, 用于 Excel 导出 + * + * @param exportReqVO 查询条件 + * @return 退款订单列表 + */ + List getRefundList(PayRefundExportReqVO exportReqVO); + + /** + * 创建退款申请 + * + * @param reqDTO 退款申请信息 + * @return 退款单号 + */ + Long createRefund(PayRefundCreateReqDTO reqDTO); + + /** + * 渠道的退款通知 + * + * @param channelId 渠道编号 + * @param notify 通知 + */ + void notifyRefund(Long channelId, PayRefundRespDTO notify); + + /** + * 同步渠道退款的退款状态 + * + * @return 同步到状态的退款数量,包括退款成功、退款失败 + */ + int syncRefund(); + +} diff --git a/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/service/refund/PayRefundServiceImpl.java b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/service/refund/PayRefundServiceImpl.java new file mode 100755 index 0000000..051d46c --- /dev/null +++ b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/service/refund/PayRefundServiceImpl.java @@ -0,0 +1,331 @@ +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.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; +import cn.iocoder.yudao.module.pay.controller.admin.refund.vo.PayRefundPageReqVO; +import cn.iocoder.yudao.module.pay.convert.refund.PayRefundConvert; +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; +import cn.iocoder.yudao.module.pay.dal.dataobject.refund.PayRefundDO; +import cn.iocoder.yudao.module.pay.dal.mysql.refund.PayRefundMapper; +import cn.iocoder.yudao.module.pay.dal.redis.no.PayNoRedisDAO; +import cn.iocoder.yudao.module.pay.enums.notify.PayNotifyTypeEnum; +import cn.iocoder.yudao.module.pay.enums.order.PayOrderStatusEnum; +import cn.iocoder.yudao.module.pay.enums.refund.PayRefundStatusEnum; +import cn.iocoder.yudao.module.pay.framework.pay.config.PayProperties; +import cn.iocoder.yudao.module.pay.service.app.PayAppService; +import cn.iocoder.yudao.module.pay.service.channel.PayChannelService; +import cn.iocoder.yudao.module.pay.service.notify.PayNotifyService; +import cn.iocoder.yudao.module.pay.service.order.PayOrderService; +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.validation.annotation.Validated; + +import java.util.List; + +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; +import static cn.iocoder.yudao.framework.common.util.json.JsonUtils.toJsonString; +import static cn.iocoder.yudao.module.pay.enums.ErrorCodeConstants.*; + +/** + * 退款订单 Service 实现类 + * + * @author jason + */ +@Service +@Slf4j +@Validated +public class PayRefundServiceImpl implements PayRefundService { + + @Resource + private PayProperties payProperties; + + @Resource + private PayRefundMapper refundMapper; + @Resource + private PayNoRedisDAO noRedisDAO; + + @Resource + private PayOrderService orderService; + @Resource + private PayAppService appService; + @Resource + private PayChannelService channelService; + @Resource + private PayNotifyService notifyService; + + @Override + public PayRefundDO getRefund(Long id) { + return refundMapper.selectById(id); + } + + @Override + public PayRefundDO getRefundByNo(String no) { + return refundMapper.selectByNo(no); + } + + @Override + public Long getRefundCountByAppId(Long appId) { + return refundMapper.selectCountByAppId(appId); + } + + @Override + public PageResult getRefundPage(PayRefundPageReqVO pageReqVO) { + return refundMapper.selectPage(pageReqVO); + } + + @Override + public List getRefundList(PayRefundExportReqVO exportReqVO) { + return refundMapper.selectList(exportReqVO); + } + + @Override + public Long createRefund(PayRefundCreateReqDTO reqDTO) { + // 1.1 校验 App + PayAppDO app = appService.validPayApp(reqDTO.getAppKey()); + // 1.2 校验支付订单 + PayOrderDO order = validatePayOrderCanRefund(reqDTO, app.getId()); + // 1.3 校验支付渠道是否有效 + PayChannelDO channel = channelService.validPayChannel(order.getChannelId()); + PayClient client = channelService.getPayClient(channel.getId()); + if (client == null) { + log.error("[refund][渠道编号({}) 找不到对应的支付客户端]", channel.getId()); + throw exception(CHANNEL_NOT_FOUND); + } + // 1.4 校验退款订单是否已经存在 + PayRefundDO refund = refundMapper.selectByAppIdAndMerchantRefundId( + app.getId(), reqDTO.getMerchantRefundId()); + if (refund != null) { + throw exception(REFUND_EXISTS); + } + + // 2.1 插入退款单 + String no = noRedisDAO.generate(payProperties.getRefundNoPrefix()); + refund = PayRefundConvert.INSTANCE.convert(reqDTO) + .setNo(no).setAppId(app.getId()).setOrderId(order.getId()).setOrderNo(order.getNo()) + .setChannelId(order.getChannelId()).setChannelCode(order.getChannelCode()) + // 商户相关的字段 + .setNotifyUrl(app.getRefundNotifyUrl()) + // 渠道相关字段 + .setChannelOrderNo(order.getChannelOrderNo()) + // 退款相关字段 + .setStatus(PayRefundStatusEnum.WAITING.getStatus()) + .setPayPrice(order.getPrice()).setRefundPrice(reqDTO.getPrice()); + refundMapper.insert(refund); + try { + // 2.2 向渠道发起退款申请 + PayRefundUnifiedReqDTO unifiedReqDTO = new PayRefundUnifiedReqDTO() + .setPayPrice(order.getPrice()) + .setRefundPrice(reqDTO.getPrice()) + .setOutTradeNo(order.getNo()) + .setOutRefundNo(refund.getNo()) + .setNotifyUrl(genChannelRefundNotifyUrl(channel)) + .setReason(reqDTO.getReason()); + PayRefundRespDTO refundRespDTO = client.unifiedRefund(unifiedReqDTO); + // 2.3 处理退款返回 + getSelf().notifyRefund(channel, refundRespDTO); + } catch (Throwable e) { + // 注意:这里仅打印异常,不进行抛出。 + // 原因是:虽然调用支付渠道进行退款发生异常(网络请求超时),实际退款成功。这个结果,后续通过退款回调、或者退款轮询补偿可以拿到。 + // 最终,在异常的情况下,支付中心会异步回调业务的退款回调接口,提供退款结果 + log.error("[createPayRefund][退款 id({}) requestDTO({}) 发生异常]", + refund.getId(), reqDTO, e); + } + + // 返回退款编号 + return refund.getId(); + } + + /** + * 校验支付订单是否可以退款 + * + * @param reqDTO 退款申请信息 + * @return 支付订单 + */ + private PayOrderDO validatePayOrderCanRefund(PayRefundCreateReqDTO reqDTO, Long appId) { + PayOrderDO order = orderService.getOrder(appId, reqDTO.getMerchantOrderId()); + if (order == null) { + throw exception(PAY_ORDER_NOT_FOUND); + } + // 校验状态,必须是已支付、或者已退款 + if (!PayOrderStatusEnum.isSuccessOrRefund(order.getStatus())) { + throw exception(PAY_ORDER_REFUND_FAIL_STATUS_ERROR); + } + + // 校验金额,退款金额不能大于原定的金额 + if (reqDTO.getPrice() + order.getRefundPrice() > order.getPrice()) { + throw exception(REFUND_PRICE_EXCEED); + } + // 是否有退款中的订单 + if (refundMapper.selectCountByAppIdAndOrderId(appId, order.getId(), + PayRefundStatusEnum.WAITING.getStatus()) > 0) { + throw exception(REFUND_HAS_REFUNDING); + } + return order; + } + + /** + * 根据支付渠道的编码,生成支付渠道的回调地址 + * + * @param channel 支付渠道 + * @return 支付渠道的回调地址 配置地址 + "/" + channel id + */ + private String genChannelRefundNotifyUrl(PayChannelDO channel) { + return payProperties.getRefundNotifyUrl() + "/" + channel.getId(); + } + + @Override + public void notifyRefund(Long channelId, PayRefundRespDTO notify) { + // 校验支付渠道是否有效 + PayChannelDO channel = channelService.validPayChannel(channelId); + // 更新退款订单 + TenantUtils.execute(channel.getTenantId(), () -> getSelf().notifyRefund(channel, notify)); + } + + /** + * 通知并更新订单的退款结果 + * + * @param channel 支付渠道 + * @param notify 通知 + */ + // 注意,如果是方法内调用该方法,需要通过 getSelf().notifyRefund(channel, notify) 调用,否则事务不生效 + @Transactional(rollbackFor = Exception.class) + public void notifyRefund(PayChannelDO channel, PayRefundRespDTO notify) { + // 情况一:退款成功 + if (PayRefundStatusEnum.isSuccess(notify.getStatus())) { + notifyRefundSuccess(channel, notify); + return; + } + // 情况二:退款失败 + if (PayRefundStatusEnum.isFailure(notify.getStatus())) { + notifyRefundFailure(channel, notify); + } + } + + private void notifyRefundSuccess(PayChannelDO channel, PayRefundRespDTO notify) { + // 1.1 查询 PayRefundDO + PayRefundDO refund = refundMapper.selectByAppIdAndNo( + channel.getAppId(), notify.getOutRefundNo()); + if (refund == null) { + throw exception(REFUND_NOT_FOUND); + } + if (PayRefundStatusEnum.isSuccess(refund.getStatus())) { // 如果已经是成功,直接返回,不用重复更新 + log.info("[notifyRefundSuccess][退款订单({}) 已经是退款成功,无需更新]", refund.getId()); + return; + } + if (!PayRefundStatusEnum.WAITING.getStatus().equals(refund.getStatus())) { + throw exception(REFUND_STATUS_IS_NOT_WAITING); + } + // 1.2 更新 PayRefundDO + PayRefundDO updateRefundObj = new PayRefundDO() + .setSuccessTime(notify.getSuccessTime()) + .setChannelRefundNo(notify.getChannelRefundNo()) + .setStatus(PayRefundStatusEnum.SUCCESS.getStatus()) + .setChannelNotifyData(toJsonString(notify)); + int updateCounts = refundMapper.updateByIdAndStatus(refund.getId(), refund.getStatus(), updateRefundObj); + if (updateCounts == 0) { // 校验状态,必须是等待状态 + throw exception(REFUND_STATUS_IS_NOT_WAITING); + } + log.info("[notifyRefundSuccess][退款订单({}) 更新为退款成功]", refund.getId()); + + // 2. 更新订单 + orderService.updateOrderRefundPrice(refund.getOrderId(), refund.getRefundPrice()); + + // 3. 插入退款通知记录 + notifyService.createPayNotifyTask(PayNotifyTypeEnum.REFUND.getType(), + refund.getId()); + } + + private void notifyRefundFailure(PayChannelDO channel, PayRefundRespDTO notify) { + // 1.1 查询 PayRefundDO + PayRefundDO refund = refundMapper.selectByAppIdAndNo( + channel.getAppId(), notify.getOutRefundNo()); + if (refund == null) { + throw exception(REFUND_NOT_FOUND); + } + if (PayRefundStatusEnum.isFailure(refund.getStatus())) { // 如果已经是成功,直接返回,不用重复更新 + log.info("[notifyRefundSuccess][退款订单({}) 已经是退款关闭,无需更新]", refund.getId()); + return; + } + if (!PayRefundStatusEnum.WAITING.getStatus().equals(refund.getStatus())) { + throw exception(REFUND_STATUS_IS_NOT_WAITING); + } + // 1.2 更新 PayRefundDO + PayRefundDO updateRefundObj = new PayRefundDO() + .setChannelRefundNo(notify.getChannelRefundNo()) + .setStatus(PayRefundStatusEnum.FAILURE.getStatus()) + .setChannelNotifyData(toJsonString(notify)) + .setChannelErrorCode(notify.getChannelErrorCode()).setChannelErrorMsg(notify.getChannelErrorMsg()); + int updateCounts = refundMapper.updateByIdAndStatus(refund.getId(), refund.getStatus(), updateRefundObj); + if (updateCounts == 0) { // 校验状态,必须是等待状态 + throw exception(REFUND_STATUS_IS_NOT_WAITING); + } + log.info("[notifyRefundFailure][退款订单({}) 更新为退款失败]", refund.getId()); + + // 2. 插入退款通知记录 + notifyService.createPayNotifyTask(PayNotifyTypeEnum.REFUND.getType(), + refund.getId()); + } + + @Override + public int syncRefund() { + // 1. 查询指定创建时间内的待退款订单 + List refunds = refundMapper.selectListByStatus(PayRefundStatusEnum.WAITING.getStatus()); + if (CollUtil.isEmpty(refunds)) { + return 0; + } + // 2. 遍历执行 + int count = 0; + for (PayRefundDO refund : refunds) { + count += syncRefund(refund) ? 1 : 0; + } + return count; + } + + /** + * 同步单个退款订单 + * + * @param refund 退款订单 + * @return 是否同步到 + */ + private boolean syncRefund(PayRefundDO refund) { + try { + // 1.1 查询退款订单信息 + PayClient payClient = channelService.getPayClient(refund.getChannelId()); + if (payClient == null) { + log.error("[syncRefund][渠道编号({}) 找不到对应的支付客户端]", refund.getChannelId()); + return false; + } + PayRefundRespDTO respDTO = payClient.getRefund(refund.getOrderNo(), refund.getNo()); + // 1.2 回调退款结果 + notifyRefund(refund.getChannelId(), respDTO); + + // 2. 如果同步到,则返回 true + return PayRefundStatusEnum.isSuccess(respDTO.getStatus()) + || PayRefundStatusEnum.isFailure(respDTO.getStatus()); + } catch (Throwable e) { + log.error("[syncRefund][refund({}) 同步退款状态异常]", refund.getId(), e); + return false; + } + } + + /** + * 获得自身的代理对象,解决 AOP 生效问题 + * + * @return 自己 + */ + private PayRefundServiceImpl getSelf() { + return SpringUtil.getBean(getClass()); + } + +} diff --git a/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/service/transfer/PayTransferService.java b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/service/transfer/PayTransferService.java new file mode 100644 index 0000000..e2a1dbd --- /dev/null +++ b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/service/transfer/PayTransferService.java @@ -0,0 +1,70 @@ +package cn.iocoder.yudao.module.pay.service.transfer; + +import cn.iocoder.yudao.framework.common.pojo.PageResult; +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; +import cn.iocoder.yudao.module.pay.dal.dataobject.transfer.PayTransferDO; +import jakarta.validation.Valid; + +/** + * 转账 Service 接口 + * + * @author jason + */ +public interface PayTransferService { + + /** + * 创建转账单,并发起转账 + * + * @param reqDTO 创建请求 + * @return 转账单编号 + */ + PayTransferCreateRespDTO createTransfer(@Valid PayTransferCreateReqDTO reqDTO); + + /** + * 获取转账单 + * @param id 转账单编号 + */ + PayTransferDO getTransfer(Long id); + + /** + * 根据转账单号获取转账单 + * + * @param no 转账单号 + * @return 转账单 + */ + PayTransferDO getTransferByNo(String no); + + /** + * 获得转账单分页 + * + * @param pageReqVO 分页查询 + * @return 转账单分页 + */ + PageResult getTransferPage(PayTransferPageReqVO pageReqVO); + + /** + * 同步渠道转账单状态 + * + * @return 同步到状态的转账数量,包括转账成功、转账失败、转账中的 + */ + int syncTransfer(); + + /** + * 【单个】同步渠道转账单状态 + * + * @param id 转账单编号 + */ + void syncTransfer(Long id); + + /** + * 渠道的转账通知 + * + * @param channelId 渠道编号 + * @param notify 通知 + */ + void notifyTransfer(Long channelId, PayTransferRespDTO notify); + +} diff --git a/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/service/transfer/PayTransferServiceImpl.java b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/service/transfer/PayTransferServiceImpl.java new file mode 100644 index 0000000..dbd12c7 --- /dev/null +++ b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/service/transfer/PayTransferServiceImpl.java @@ -0,0 +1,319 @@ +package cn.iocoder.yudao.module.pay.service.transfer; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.util.ObjectUtil; +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.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; +import cn.iocoder.yudao.module.pay.controller.admin.transfer.vo.PayTransferPageReqVO; +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.transfer.PayTransferDO; +import cn.iocoder.yudao.module.pay.dal.mysql.transfer.PayTransferMapper; +import cn.iocoder.yudao.module.pay.dal.redis.no.PayNoRedisDAO; +import cn.iocoder.yudao.module.pay.enums.notify.PayNotifyTypeEnum; +import cn.iocoder.yudao.module.pay.enums.transfer.PayTransferStatusEnum; +import cn.iocoder.yudao.module.pay.framework.pay.config.PayProperties; +import cn.iocoder.yudao.module.pay.service.app.PayAppService; +import cn.iocoder.yudao.module.pay.service.channel.PayChannelService; +import cn.iocoder.yudao.module.pay.service.notify.PayNotifyService; +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; + +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; +import static cn.iocoder.yudao.module.pay.enums.ErrorCodeConstants.*; + +// TODO @jason:等彻底实现完,单测写写; + +/** + * 转账 Service 实现类 + * + * @author jason + */ +@Service +@Slf4j +public class PayTransferServiceImpl implements PayTransferService { + + private static final String TRANSFER_NO_PREFIX = "T"; + + @Resource + private PayProperties payProperties; + + @Resource + private PayTransferMapper transferMapper; + @Resource + private PayAppService appService; + @Resource + private PayChannelService channelService; + @Resource + private PayNotifyService notifyService; + @Resource + private PayNoRedisDAO noRedisDAO; + + @Override + public PayTransferCreateRespDTO createTransfer(PayTransferCreateReqDTO reqDTO) { + // 1.1 校验 App + PayAppDO payApp = appService.validPayApp(reqDTO.getAppKey()); + // 1.2 校验支付渠道是否有效 + PayChannelDO channel = channelService.validPayChannel(payApp.getId(), reqDTO.getChannelCode()); + PayClient client = channelService.getPayClient(channel.getId()); + if (client == null) { + log.error("[createTransfer][渠道编号({}) 找不到对应的支付客户端]", channel.getId()); + throw exception(CHANNEL_NOT_FOUND); + } + // 1.3 校验转账单已经发起过转账 + PayTransferDO transfer = validateTransferCanCreate(reqDTO, payApp.getId()); + + // 2.1 情况一:不存在创建转账单,则进行创建 + if (transfer == null) { + String no = noRedisDAO.generate(TRANSFER_NO_PREFIX); + transfer = BeanUtils.toBean(reqDTO, PayTransferDO.class) + .setAppId(channel.getAppId()).setChannelId(channel.getId()) + .setNo(no).setStatus(PayTransferStatusEnum.WAITING.getStatus()) + .setNotifyUrl(payApp.getTransferNotifyUrl()); + transferMapper.insert(transfer); + } else { + // 2.2 情况二:存在创建转账单,但是状态为关闭,则更新为等待中 + transferMapper.updateByIdAndStatus(transfer.getId(), transfer.getStatus(), + new PayTransferDO().setStatus(PayTransferStatusEnum.WAITING.getStatus())); + } + PayTransferRespDTO unifiedTransferResp = null; + try { + // 3. 调用三方渠道发起转账 + PayTransferUnifiedReqDTO transferUnifiedReq = BeanUtils.toBean(reqDTO, PayTransferUnifiedReqDTO.class) + .setOutTransferNo(transfer.getNo()) + .setNotifyUrl(genChannelTransferNotifyUrl(channel)); + unifiedTransferResp = client.unifiedTransfer(transferUnifiedReq); + // 4. 通知转账结果 + getSelf().notifyTransfer(channel, unifiedTransferResp); + } catch (Throwable e) { + // 注意这里仅打印异常,不进行抛出。 + // 原因是:虽然调用支付渠道进行转账发生异常(网络请求超时),实际转账成功。这个结果,后续转账轮询可以拿到。 + // 或者,使用相同 no 再次发起转账请求 + log.error("[createTransfer][转账编号({}) requestDTO({}) 发生异常]", transfer.getId(), reqDTO, e); + } + return new PayTransferCreateRespDTO().setId(transfer.getId()) + .setChannelPackageInfo(unifiedTransferResp != null ? unifiedTransferResp.getChannelPackageInfo() : null); + } + + /** + * 根据支付渠道的编码,生成支付渠道的回调地址 + * + * @param channel 支付渠道 + * @return 支付渠道的回调地址 配置地址 + "/" + channel id + */ + private String genChannelTransferNotifyUrl(PayChannelDO channel) { + return payProperties.getTransferNotifyUrl() + "/" + channel.getId(); + } + + private PayTransferDO validateTransferCanCreate(PayTransferCreateReqDTO reqDTO, Long appId) { + PayTransferDO transfer = transferMapper.selectByAppIdAndMerchantOrderId(appId, reqDTO.getMerchantTransferId()); + if (transfer != null) { + // 只有转账单状态为关闭,才能再次发起转账 + if (!PayTransferStatusEnum.isClosed(transfer.getStatus())) { + throw exception(PAY_TRANSFER_CREATE_FAIL_STATUS_NOT_CLOSED); + } + // 校验参数是否一致 + if (ObjectUtil.notEqual(reqDTO.getPrice(), transfer.getPrice())) { + throw exception(PAY_TRANSFER_CREATE_PRICE_NOT_MATCH); + } + if (ObjectUtil.notEqual(reqDTO.getChannelCode(), transfer.getChannelCode())) { + throw exception(PAY_TRANSFER_CREATE_CHANNEL_NOT_MATCH); + } + } + // 如果状态为等待状态:不知道渠道转账是否发起成功 + // 特殊:允许使用相同的 no 再次发起转账,渠道会保证幂等 + return transfer; + } + + @Transactional(rollbackFor = Exception.class) + // 注意,如果是方法内调用该方法,需要通过 getSelf().notifyTransfer(channel, notify) 调用,否则事务不生效 + public void notifyTransfer(PayChannelDO channel, PayTransferRespDTO notify) { + // 转账成功的回调 + if (PayTransferStatusEnum.isSuccess(notify.getStatus())) { + notifyTransferSuccess(channel, notify); + } + // 转账关闭的回调 + if (PayTransferStatusEnum.isClosed(notify.getStatus())) { + notifyTransferClosed(channel, notify); + } + // 转账处理中的回调 + if (PayTransferStatusEnum.isProcessing(notify.getStatus())) { + notifyTransferProgressing(channel, notify); + } + // WAITING 状态无需处理 + } + + private void notifyTransferProgressing(PayChannelDO channel, PayTransferRespDTO notify) { + // 1. 校验 + PayTransferDO transfer = transferMapper.selectByAppIdAndNo(channel.getAppId(), notify.getOutTransferNo()); + if (transfer == null) { + throw exception(PAY_TRANSFER_NOT_FOUND); + } + if (PayTransferStatusEnum.isProcessing(transfer.getStatus())) { // 如果已经是转账中,直接返回,不用重复更新 + log.info("[notifyTransferProgressing][transfer({}) 已经是转账中状态,无需更新]", transfer.getId()); + return; + } + if (!PayTransferStatusEnum.isWaiting(transfer.getStatus())) { + throw exception(PAY_TRANSFER_NOTIFY_FAIL_STATUS_IS_NOT_WAITING); + } + + // 2. 更新状态 + int updateCounts = transferMapper.updateByIdAndStatus(transfer.getId(), + PayTransferStatusEnum.WAITING.getStatus(), + new PayTransferDO().setStatus(PayTransferStatusEnum.PROCESSING.getStatus()) + .setChannelPackageInfo(notify.getChannelPackageInfo())); + if (updateCounts == 0) { + throw exception(PAY_TRANSFER_NOTIFY_FAIL_STATUS_IS_NOT_WAITING); + } + log.info("[notifyTransferProgressing][transfer({}) 更新为转账进行中状态]", transfer.getId()); + } + + private void notifyTransferSuccess(PayChannelDO channel, PayTransferRespDTO notify) { + // 1. 校验状态 + PayTransferDO transfer = transferMapper.selectByAppIdAndNo(channel.getAppId(), notify.getOutTransferNo()); + if (transfer == null) { + throw exception(PAY_TRANSFER_NOT_FOUND); + } + if (PayTransferStatusEnum.isSuccess(transfer.getStatus())) { // 如果已成功,直接返回,不用重复更新 + log.info("[notifyTransferSuccess][transfer({}) 已经是成功状态,无需更新]", transfer.getId()); + return; + } + if (!PayTransferStatusEnum.isWaitingOrProcessing(transfer.getStatus())) { + throw exception(PAY_TRANSFER_NOTIFY_FAIL_STATUS_NOT_WAITING_OR_PROCESSING); + } + + // 2. 更新状态 + int updateCounts = transferMapper.updateByIdAndStatus(transfer.getId(), + CollUtil.newArrayList(PayTransferStatusEnum.WAITING.getStatus(), PayTransferStatusEnum.PROCESSING.getStatus()), + new PayTransferDO().setStatus(PayTransferStatusEnum.SUCCESS.getStatus()) + .setSuccessTime(notify.getSuccessTime()) + .setChannelTransferNo(notify.getChannelTransferNo()) + .setChannelNotifyData(JsonUtils.toJsonString(notify))); + if (updateCounts == 0) { + throw exception(PAY_TRANSFER_NOTIFY_FAIL_STATUS_NOT_WAITING_OR_PROCESSING); + } + log.info("[notifyTransferSuccess][transfer({}) 更新为已转账]", transfer.getId()); + + // 3. 插入转账通知记录 + notifyService.createPayNotifyTask(PayNotifyTypeEnum.TRANSFER.getType(), transfer.getId()); + } + + private void notifyTransferClosed(PayChannelDO channel, PayTransferRespDTO notify) { + // 1. 校验状态 + PayTransferDO transfer = transferMapper.selectByAppIdAndNo(channel.getAppId(), notify.getOutTransferNo()); + if (transfer == null) { + throw exception(PAY_TRANSFER_NOT_FOUND); + } + if (PayTransferStatusEnum.isClosed(transfer.getStatus())) { // 如果已是关闭状态,直接返回,不用重复更新 + log.info("[notifyTransferClosed][transfer({}) 已经是关闭状态,无需更新]", transfer.getId()); + return; + } + if (!PayTransferStatusEnum.isWaitingOrProcessing(transfer.getStatus())) { + throw exception(PAY_TRANSFER_NOTIFY_FAIL_STATUS_NOT_WAITING_OR_PROCESSING); + } + + // 2. 更新状态 + int updateCount = transferMapper.updateByIdAndStatus(transfer.getId(), + CollUtil.newArrayList(PayTransferStatusEnum.WAITING.getStatus(), PayTransferStatusEnum.PROCESSING.getStatus()), + new PayTransferDO().setStatus(PayTransferStatusEnum.CLOSED.getStatus()) + .setChannelTransferNo(notify.getChannelTransferNo()) + .setChannelNotifyData(JsonUtils.toJsonString(notify)) + .setChannelErrorCode(notify.getChannelErrorCode()).setChannelErrorMsg(notify.getChannelErrorMsg())); + if (updateCount == 0) { + throw exception(PAY_TRANSFER_NOTIFY_FAIL_STATUS_NOT_WAITING_OR_PROCESSING); + } + log.info("[notifyTransferClosed][transfer({}) 更新为关闭状态]", transfer.getId()); + + // 3. 插入转账通知记录 + notifyService.createPayNotifyTask(PayNotifyTypeEnum.TRANSFER.getType(), transfer.getId()); + } + + @Override + public PayTransferDO getTransfer(Long id) { + return transferMapper.selectById(id); + } + + @Override + public PayTransferDO getTransferByNo(String no) { + return transferMapper.selectByNo(no); + } + + @Override + public PageResult getTransferPage(PayTransferPageReqVO pageReqVO) { + return transferMapper.selectPage(pageReqVO); + } + + @Override + public int syncTransfer() { + List list = transferMapper.selectListByStatus(CollUtil.newArrayList( + PayTransferStatusEnum.WAITING.getStatus(), PayTransferStatusEnum.PROCESSING.getStatus())); + if (CollUtil.isEmpty(list)) { + return 0; + } + int count = 0; + for (PayTransferDO transfer : list) { + if (!transfer.getId().equals(54L)) { + continue; + } + count += syncTransfer(transfer) ? 1 : 0; + } + return count; + } + + @Override + public void syncTransfer(Long id) { + PayTransferDO transfer = transferMapper.selectById(id); + if (transfer == null) { + throw exception(PAY_TRANSFER_NOT_FOUND); + } + syncTransfer(transfer); + } + + private boolean syncTransfer(PayTransferDO transfer) { + try { + // 1. 查询转账订单信息 + PayClient payClient = channelService.getPayClient(transfer.getChannelId()); + if (payClient == null) { + log.error("[syncTransfer][渠道编号({}) 找不到对应的支付客户端]", transfer.getChannelId()); + return false; + } + PayTransferRespDTO resp = payClient.getTransfer(transfer.getNo()); + + // 2. 回调转账结果 + notifyTransfer(transfer.getChannelId(), resp); + return true; + } catch (Throwable ex) { + log.error("[syncTransfer][transfer({}) 同步转账单状态异常]", transfer.getId(), ex); + return false; + } + } + + public void notifyTransfer(Long channelId, PayTransferRespDTO notify) { + // 校验渠道是否有效 + PayChannelDO channel = channelService.validPayChannel(channelId); + // 通知转账结果给对应的业务 + TenantUtils.execute(channel.getTenantId(), () -> getSelf().notifyTransfer(channel, notify)); + } + + /** + * 获得自身的代理对象,解决 AOP 生效问题 + * + * @return 自己 + */ + private PayTransferServiceImpl getSelf() { + return SpringUtil.getBean(getClass()); + } + +} diff --git a/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/service/wallet/PayWalletRechargePackageService.java b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/service/wallet/PayWalletRechargePackageService.java new file mode 100644 index 0000000..06e4632 --- /dev/null +++ b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/service/wallet/PayWalletRechargePackageService.java @@ -0,0 +1,71 @@ +package cn.iocoder.yudao.module.pay.service.wallet; + +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.module.pay.controller.admin.wallet.vo.rechargepackage.WalletRechargePackageCreateReqVO; +import cn.iocoder.yudao.module.pay.controller.admin.wallet.vo.rechargepackage.WalletRechargePackagePageReqVO; +import cn.iocoder.yudao.module.pay.controller.admin.wallet.vo.rechargepackage.WalletRechargePackageUpdateReqVO; +import cn.iocoder.yudao.module.pay.dal.dataobject.wallet.PayWalletRechargePackageDO; + +import jakarta.validation.Valid; +import java.util.Collection; +import java.util.List; + +/** + * 钱包充值套餐 Service 接口 + * + * @author jason + */ +public interface PayWalletRechargePackageService { + + /** + * 获取钱包充值套餐 + * @param packageId 充值套餐编号 + */ + PayWalletRechargePackageDO getWalletRechargePackage(Long packageId); + + /** + * 校验钱包充值套餐的有效性, 无效的话抛出 ServiceException 异常 + * + * @param packageId 充值套餐编号 + */ + PayWalletRechargePackageDO validWalletRechargePackage(Long packageId); + + /** + * 创建充值套餐 + * + * @param createReqVO 创建信息 + * @return 编号 + */ + Long createWalletRechargePackage(@Valid WalletRechargePackageCreateReqVO createReqVO); + + /** + * 更新充值套餐 + * + * @param updateReqVO 更新信息 + */ + void updateWalletRechargePackage(@Valid WalletRechargePackageUpdateReqVO updateReqVO); + + /** + * 删除充值套餐 + * + * @param id 编号 + */ + void deleteWalletRechargePackage(Long id); + + /** + * 获得充值套餐分页 + * + * @param pageReqVO 分页查询 + * @return 充值套餐分页 + */ + PageResult getWalletRechargePackagePage(WalletRechargePackagePageReqVO pageReqVO); + + /** + * 获得充值套餐列表 + * + * @param status 状态 + * @return 充值套餐列表 + */ + List getWalletRechargePackageList(Integer status); + +} diff --git a/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/service/wallet/PayWalletRechargePackageServiceImpl.java b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/service/wallet/PayWalletRechargePackageServiceImpl.java new file mode 100644 index 0000000..82fa676 --- /dev/null +++ b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/service/wallet/PayWalletRechargePackageServiceImpl.java @@ -0,0 +1,113 @@ +package cn.iocoder.yudao.module.pay.service.wallet; + +import cn.hutool.core.util.StrUtil; +import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.module.pay.controller.admin.wallet.vo.rechargepackage.WalletRechargePackageCreateReqVO; +import cn.iocoder.yudao.module.pay.controller.admin.wallet.vo.rechargepackage.WalletRechargePackagePageReqVO; +import cn.iocoder.yudao.module.pay.controller.admin.wallet.vo.rechargepackage.WalletRechargePackageUpdateReqVO; +import cn.iocoder.yudao.module.pay.convert.wallet.PayWalletRechargePackageConvert; +import cn.iocoder.yudao.module.pay.dal.dataobject.wallet.PayWalletRechargePackageDO; +import cn.iocoder.yudao.module.pay.dal.mysql.wallet.PayWalletRechargePackageMapper; +import org.springframework.stereotype.Service; + +import jakarta.annotation.Resource; + +import java.util.List; + +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; +import static cn.iocoder.yudao.module.pay.enums.ErrorCodeConstants.*; + +/** + * 钱包充值套餐 Service 实现类 + * + * @author jason + */ +@Service +public class PayWalletRechargePackageServiceImpl implements PayWalletRechargePackageService { + + @Resource + private PayWalletRechargePackageMapper walletRechargePackageMapper; + + @Override + public PayWalletRechargePackageDO getWalletRechargePackage(Long packageId) { + return walletRechargePackageMapper.selectById(packageId); + } + + @Override + public PayWalletRechargePackageDO validWalletRechargePackage(Long packageId) { + PayWalletRechargePackageDO rechargePackageDO = walletRechargePackageMapper.selectById(packageId); + if (rechargePackageDO == null) { + throw exception(WALLET_RECHARGE_PACKAGE_NOT_FOUND); + } + if (CommonStatusEnum.DISABLE.getStatus().equals(rechargePackageDO.getStatus())) { + throw exception(WALLET_RECHARGE_PACKAGE_IS_DISABLE); + } + return rechargePackageDO; + } + + @Override + public Long createWalletRechargePackage(WalletRechargePackageCreateReqVO createReqVO) { + // 校验套餐名是否唯一 + validateRechargePackageNameUnique(null, createReqVO.getName()); + + // 插入 + PayWalletRechargePackageDO walletRechargePackage = PayWalletRechargePackageConvert.INSTANCE.convert(createReqVO); + walletRechargePackageMapper.insert(walletRechargePackage); + // 返回 + return walletRechargePackage.getId(); + } + + @Override + public void updateWalletRechargePackage(WalletRechargePackageUpdateReqVO updateReqVO) { + // 校验存在 + validateWalletRechargePackageExists(updateReqVO.getId()); + // 校验套餐名是否唯一 + validateRechargePackageNameUnique(updateReqVO.getId(), updateReqVO.getName()); + + // 更新 + PayWalletRechargePackageDO updateObj = PayWalletRechargePackageConvert.INSTANCE.convert(updateReqVO); + walletRechargePackageMapper.updateById(updateObj); + } + + private void validateRechargePackageNameUnique(Long id, String name) { + if (StrUtil.isBlank(name)) { + return; + } + PayWalletRechargePackageDO rechargePackage = walletRechargePackageMapper.selectByName(name); + if (rechargePackage == null) { + return ; + } + if (id == null) { + throw exception(WALLET_RECHARGE_PACKAGE_NAME_EXISTS); + } + if (!id.equals(rechargePackage.getId())) { + throw exception(WALLET_RECHARGE_PACKAGE_NAME_EXISTS); + } + } + + @Override + public void deleteWalletRechargePackage(Long id) { + // 校验存在 + validateWalletRechargePackageExists(id); + // 删除 + walletRechargePackageMapper.deleteById(id); + } + + private void validateWalletRechargePackageExists(Long id) { + if (walletRechargePackageMapper.selectById(id) == null) { + throw exception(WALLET_RECHARGE_PACKAGE_NOT_FOUND); + } + } + + @Override + public PageResult getWalletRechargePackagePage(WalletRechargePackagePageReqVO pageReqVO) { + return walletRechargePackageMapper.selectPage(pageReqVO); + } + + @Override + public List getWalletRechargePackageList(Integer status) { + return walletRechargePackageMapper.selectListByStatus(status); + } + +} diff --git a/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/service/wallet/PayWalletRechargeService.java b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/service/wallet/PayWalletRechargeService.java new file mode 100644 index 0000000..f2ab50e --- /dev/null +++ b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/service/wallet/PayWalletRechargeService.java @@ -0,0 +1,64 @@ +package cn.iocoder.yudao.module.pay.service.wallet; + +import cn.iocoder.yudao.framework.common.pojo.PageParam; +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.module.pay.controller.app.wallet.vo.recharge.AppPayWalletRechargeCreateReqVO; +import cn.iocoder.yudao.module.pay.dal.dataobject.wallet.PayWalletRechargeDO; + +/** + * 钱包充值 Service 接口 + * + * @author jason + */ +public interface PayWalletRechargeService { + + /** + * 创建钱包充值记录(发起充值) + * + * @param userId 用户编号 + * @param userType 用户类型 + * @param createReqVO 钱包充值请求 VO + * @param userIp 用户Ip + * @return 钱包充值记录 + */ + PayWalletRechargeDO createWalletRecharge(Long userId, Integer userType, String userIp, + AppPayWalletRechargeCreateReqVO createReqVO); + + /** + * 获得钱包充值记录分页 + * + * @param userId 用户编号 + * @param userType 用户类型 + * @param pageReqVO 分页请求 + * @param payStatus 是否支付 + * @return 钱包充值记录分页 + */ + PageResult getWalletRechargePackagePage(Long userId, Integer userType, + PageParam pageReqVO, Boolean payStatus); + + /** + * 更新钱包充值成功 + * + * @param id 钱包充值记录编号 + * @param payOrderId 支付订单编号 + */ + void updateWalletRechargerPaid(Long id, Long payOrderId); + + /** + * 发起钱包充值退款 + * + * @param id 钱包充值编号 + * @param userIp 用户 ip 地址 + */ + void refundWalletRecharge(Long id, String userIp); + + /** + * 更新钱包充值记录为已退款 + * + * @param id 钱包充值记录编号 + * @param refundId 钱包充值退款编号(实际和 id 相同) + * @param payRefundId 退款单id + */ + void updateWalletRechargeRefunded(Long id, Long refundId, Long payRefundId); + +} diff --git a/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/service/wallet/PayWalletRechargeServiceImpl.java b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/service/wallet/PayWalletRechargeServiceImpl.java new file mode 100644 index 0000000..e4cda98 --- /dev/null +++ b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/service/wallet/PayWalletRechargeServiceImpl.java @@ -0,0 +1,331 @@ +package cn.iocoder.yudao.module.pay.service.wallet; + +import cn.hutool.core.date.LocalDateTimeUtil; +import cn.hutool.core.lang.Assert; +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.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; +import cn.iocoder.yudao.module.pay.api.refund.dto.PayRefundRespDTO; +import cn.iocoder.yudao.module.pay.controller.app.wallet.vo.recharge.AppPayWalletRechargeCreateReqVO; +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.dal.dataobject.wallet.PayWalletRechargeDO; +import cn.iocoder.yudao.module.pay.dal.dataobject.wallet.PayWalletRechargePackageDO; +import cn.iocoder.yudao.module.pay.dal.mysql.wallet.PayWalletRechargeMapper; +import cn.iocoder.yudao.module.pay.enums.order.PayOrderStatusEnum; +import cn.iocoder.yudao.module.pay.enums.refund.PayRefundStatusEnum; +import cn.iocoder.yudao.module.pay.enums.wallet.PayWalletBizTypeEnum; +import cn.iocoder.yudao.module.pay.framework.pay.config.PayProperties; +import cn.iocoder.yudao.module.pay.service.order.PayOrderService; +import cn.iocoder.yudao.module.system.api.social.SocialClientApi; +import cn.iocoder.yudao.module.system.api.social.dto.SocialWxaSubscribeMessageSendReqDTO; +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import org.springframework.scheduling.annotation.Async; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.time.Duration; +import java.time.LocalDateTime; +import java.util.Objects; + +import static cn.hutool.core.util.ObjectUtil.notEqual; +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; +import static cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.addTime; +import static cn.iocoder.yudao.framework.common.util.json.JsonUtils.toJsonString; +import static cn.iocoder.yudao.framework.common.util.number.MoneyUtils.fenToYuanStr; +import static cn.iocoder.yudao.module.pay.convert.wallet.PayWalletRechargeConvert.INSTANCE; +import static cn.iocoder.yudao.module.pay.enums.ErrorCodeConstants.*; +import static cn.iocoder.yudao.module.pay.enums.MessageTemplateConstants.WXA_WALLET_RECHARGER_PAID; +import static cn.iocoder.yudao.module.pay.enums.refund.PayRefundStatusEnum.*; + +/** + * 钱包充值 Service 实现类 + * + * @author jason + */ +@Service +@Slf4j +public class PayWalletRechargeServiceImpl implements PayWalletRechargeService { + + private static final String WALLET_RECHARGE_ORDER_SUBJECT = "钱包余额充值"; + + @Resource + private PayWalletRechargeMapper walletRechargeMapper; + @Resource + private PayWalletService payWalletService; + @Resource + private PayOrderService payOrderService; +// @Resource +// private PayRefundService payRefundService; + @Resource + private PayWalletRechargePackageService payWalletRechargePackageService; + + @Resource + public SocialClientApi socialClientApi; + @Resource + private PayRefundApi payRefundApi; + + @Resource + private PayProperties payProperties; + + @Override + @Transactional(rollbackFor = Exception.class) + public PayWalletRechargeDO createWalletRecharge(Long userId, Integer userType, String userIp, + AppPayWalletRechargeCreateReqVO reqVO) { + // 1.1 计算充值金额 + int payPrice; + int bonusPrice = 0; + if (Objects.nonNull(reqVO.getPackageId())) { + PayWalletRechargePackageDO rechargePackage = payWalletRechargePackageService.validWalletRechargePackage(reqVO.getPackageId()); + payPrice = rechargePackage.getPayPrice(); + bonusPrice = rechargePackage.getBonusPrice(); + } else { + payPrice = reqVO.getPayPrice(); + } + // 1.2 插入充值记录 + PayWalletDO wallet = payWalletService.getOrCreateWallet(userId, userType); + PayWalletRechargeDO recharge = INSTANCE.convert(wallet.getId(), payPrice, bonusPrice, reqVO.getPackageId()); + walletRechargeMapper.insert(recharge); + + // 2.1 创建支付单 + Long payOrderId = payOrderService.createOrder(new PayOrderCreateReqDTO() + .setAppKey(payProperties.getWalletPayAppKey()).setUserIp(userIp) + .setMerchantOrderId(recharge.getId().toString()) // 业务的订单编号 + .setSubject(WALLET_RECHARGE_ORDER_SUBJECT).setBody("") + .setPrice(recharge.getPayPrice()) + .setExpireTime(addTime(Duration.ofHours(2L)))); // TODO @芋艿:支付超时时间 + // 2.2 更新钱包充值记录中支付订单 + walletRechargeMapper.updateById(new PayWalletRechargeDO().setId(recharge.getId()).setPayOrderId(payOrderId)); + recharge.setPayOrderId(payOrderId); + return recharge; + } + + @Override + public PageResult getWalletRechargePackagePage(Long userId, Integer userType, + PageParam pageReqVO, Boolean payStatus) { + PayWalletDO wallet = payWalletService.getOrCreateWallet(userId, userType); + return walletRechargeMapper.selectPage(pageReqVO, wallet.getId(), payStatus); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void updateWalletRechargerPaid(Long id, Long payOrderId) { + // 1.1 校验钱包充值是否存在 + PayWalletRechargeDO recharge = walletRechargeMapper.selectById(id); + if (recharge == null) { + log.error("[updateWalletRechargerPaid][recharge({}) payOrder({}) 不存在充值订单,请进行处理!]", id, payOrderId); + throw exception(WALLET_RECHARGE_NOT_FOUND); + } + // 1.2 校验钱包充值是否可以支付 + if (recharge.getPayStatus()) { + // 特殊:支付单号相同,直接返回,说明重复回调 + if (ObjectUtil.equals(recharge.getPayOrderId(), payOrderId)) { + log.warn("[updateWalletRechargerPaid][recharge({}) 已支付,且支付单号相同({}),直接返回]", recharge, payOrderId); + return; + } + // 异常:支付单号不同,说明支付单号错误 + log.error("[updateWalletRechargerPaid][recharge({}) 已支付,但是支付单号不同({}),请进行处理!]", recharge, payOrderId); + throw exception(WALLET_RECHARGE_UPDATE_PAID_PAY_ORDER_ID_ERROR); + } + + // 2. 校验支付订单的合法性 + PayOrderDO payOrderDO = validatePayOrderPaid(recharge, payOrderId); + + // 3. 更新钱包充值的支付状态 + int updateCount = walletRechargeMapper.updateByIdAndPaid(id, false, + new PayWalletRechargeDO().setId(id).setPayStatus(true).setPayTime(LocalDateTime.now()) + .setPayChannelCode(payOrderDO.getChannelCode())); + if (updateCount == 0) { + throw exception(WALLET_RECHARGE_UPDATE_PAID_STATUS_NOT_UNPAID); + } + + // 4. 更新钱包余额 + // TODO @jason:这样的话,未来提现会不会把充值的,也提现走哈。类似先充 100,送 110;然后提现 110; + // TODO 需要钱包中加个可提现余额 + payWalletService.addWalletBalance(recharge.getWalletId(), String.valueOf(id), + PayWalletBizTypeEnum.RECHARGE, recharge.getTotalPrice()); + + // 5. 发送订阅消息 + getSelf().sendWalletRechargerPaidMessage(payOrderId, recharge); + } + + @Async + public void sendWalletRechargerPaidMessage(Long payOrderId, PayWalletRechargeDO walletRecharge) { + // 1. 获得会员钱包信息 + PayWalletDO wallet = payWalletService.getWallet(walletRecharge.getWalletId()); + // 2. 构建并发送模版消息 + socialClientApi.sendWxaSubscribeMessage(new SocialWxaSubscribeMessageSendReqDTO() + .setUserId(wallet.getUserId()).setUserType(wallet.getUserType()) + .setTemplateTitle(WXA_WALLET_RECHARGER_PAID) + .setPage("pages/user/wallet/money") // 钱包详情界面 + .addMessage("character_string1", String.valueOf(payOrderId)) // 支付单编号 + .addMessage("amount2", fenToYuanStr(walletRecharge.getTotalPrice())) // 充值金额 + .addMessage("time3", LocalDateTimeUtil.formatNormal(walletRecharge.getCreateTime())) // 充值时间 + .addMessage("phrase4", "充值成功")); // 充值状态 + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void refundWalletRecharge(Long id, String userIp) { + // 1.1 获取钱包充值记录 + PayWalletRechargeDO walletRecharge = walletRechargeMapper.selectById(id); + if (walletRecharge == null) { + log.error("[refundWalletRecharge][钱包充值记录不存在,钱包充值记录 id({})]", id); + throw exception(WALLET_RECHARGE_NOT_FOUND); + } + // 1.2 校验钱包充值是否可以发起退款 + PayWalletDO wallet = validateWalletRechargeCanRefund(walletRecharge); + + // 2. 冻结退款的余额,暂时只处理赠送的余额也全部退回 + payWalletService.freezePrice(wallet.getId(), walletRecharge.getTotalPrice()); + + // 3. 创建退款单 + String walletRechargeId = String.valueOf(id); + String refundId = walletRechargeId + "-refund"; + Long payRefundId = payRefundApi.createRefund(new PayRefundCreateReqDTO() + .setAppKey(payProperties.getWalletPayAppKey()).setUserIp(userIp) + .setMerchantOrderId(walletRechargeId) + .setMerchantRefundId(refundId) + .setReason("想退钱").setPrice(walletRecharge.getPayPrice())); + + // 4. 更新充值记录退款单号 + walletRechargeMapper.updateById(new PayWalletRechargeDO().setPayRefundId(payRefundId) + .setRefundStatus(WAITING.getStatus()).setId(walletRecharge.getId())); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void updateWalletRechargeRefunded(Long id, Long refundId, Long payRefundId) { + // 1.1 获取钱包充值记录 + // 说明:因为 id 和 refundId 是相同的,所以直接使用 id 查询即可! + PayWalletRechargeDO walletRecharge = walletRechargeMapper.selectById(id); + if (walletRecharge == null) { + log.error("[updateWalletRechargerPaid][钱包充值记录不存在,钱包充值记录 id({})]", id); + throw exception(WALLET_RECHARGE_NOT_FOUND); + } + // 1.2 校验钱包充值是否可以更新已退款 + PayRefundRespDTO payRefund = validateWalletRechargeCanRefunded(walletRecharge, payRefundId); + + // 2. 处理退款结果 + PayWalletRechargeDO updateObj = new PayWalletRechargeDO().setId(id); + // 情况一:退款成功 + if (PayRefundStatusEnum.isSuccess(payRefund.getStatus())) { + // 2.1 更新钱包余额 + payWalletService.reduceWalletBalance(walletRecharge.getWalletId(), id, + PayWalletBizTypeEnum.RECHARGE_REFUND, walletRecharge.getTotalPrice()); + + updateObj.setRefundStatus(SUCCESS.getStatus()).setRefundTime(payRefund.getSuccessTime()) + .setRefundTotalPrice(walletRecharge.getTotalPrice()).setRefundPayPrice(walletRecharge.getPayPrice()) + .setRefundBonusPrice(walletRecharge.getBonusPrice()); + // 情况二:退款失败 + } else if (PayRefundStatusEnum.isFailure(payRefund.getStatus())) { + // 2.2 解冻余额 + payWalletService.unfreezePrice(walletRecharge.getWalletId(), walletRecharge.getTotalPrice()); + + updateObj.setRefundStatus(FAILURE.getStatus()); + } + + // 3. 更新钱包充值的退款字段 + walletRechargeMapper.updateByIdAndRefunded(id, WAITING.getStatus(), updateObj); + } + + private PayRefundRespDTO validateWalletRechargeCanRefunded(PayWalletRechargeDO walletRecharge, Long payRefundId) { + // 1. 校验退款订单匹配 + if (notEqual(walletRecharge.getPayRefundId(), payRefundId)) { + log.error("[validateWalletRechargeCanRefunded][钱包充值({}) 退款单不匹配({}),请进行处理!钱包充值的数据是:{}]", + walletRecharge.getId(), payRefundId, toJsonString(walletRecharge)); + throw exception(WALLET_RECHARGE_REFUND_FAIL_REFUND_ORDER_ID_ERROR); + } + + // 2.1 校验退款订单 + PayRefundRespDTO payRefund = payRefundApi.getRefund(payRefundId); + if (payRefund == null) { + log.error("[validateWalletRechargeCanRefunded][payRefund({})不存在]", payRefundId); + throw exception(WALLET_RECHARGE_REFUND_FAIL_REFUND_NOT_FOUND); + } + // 2.2 校验退款金额一致 + if (notEqual(payRefund.getRefundPrice(), walletRecharge.getPayPrice())) { + log.error("[validateWalletRechargeCanRefunded][钱包({}) payRefund({}) 退款金额不匹配,请进行处理!钱包数据是:{},payRefund 数据是:{}]", + walletRecharge.getId(), payRefundId, toJsonString(walletRecharge), toJsonString(payRefund)); + throw exception(WALLET_RECHARGE_REFUND_FAIL_REFUND_PRICE_NOT_MATCH); + } + // 2.3 校验退款订单商户订单是否匹配 + if (notEqual(payRefund.getMerchantRefundId(), walletRecharge.getId().toString())) { + log.error("[validateWalletRechargeCanRefunded][钱包({}) 退款单不匹配({}),请进行处理!payRefund 数据是:{}]", + walletRecharge.getId(), payRefundId, toJsonString(payRefund)); + throw exception(WALLET_RECHARGE_REFUND_FAIL_REFUND_ORDER_ID_ERROR); + } + return payRefund; + } + + private PayWalletDO validateWalletRechargeCanRefund(PayWalletRechargeDO walletRecharge) { + // 校验充值订单是否支付 + if (!walletRecharge.getPayStatus()) { + throw exception(WALLET_RECHARGE_REFUND_FAIL_NOT_PAID); + } + // 校验充值订单是否已退款 + if (walletRecharge.getPayRefundId() != null) { + throw exception(WALLET_RECHARGE_REFUND_FAIL_REFUNDED); + } + // 校验钱包余额是否足够 + PayWalletDO wallet = payWalletService.getWallet(walletRecharge.getWalletId()); + Assert.notNull(wallet, "用户钱包({}) 不存在", wallet.getId()); + if (wallet.getBalance() < walletRecharge.getTotalPrice()) { + throw exception(WALLET_RECHARGE_REFUND_BALANCE_NOT_ENOUGH); + } + // TODO @芋艿:需要考虑下,赠送的金额,会不会导致提现超过; + return wallet; + } + + /** + * 校验支付订单的合法性 + * + * @param recharge 充值订单 + * @param payOrderId 支付订单编号 + * @return 支付订单 + */ + private PayOrderDO validatePayOrderPaid(PayWalletRechargeDO recharge, Long payOrderId) { + // 1. 校验支付单是否存在 + PayOrderDO payOrder = payOrderService.getOrder(payOrderId); + if (payOrder == null) { + log.error("[validatePayOrderPaid][充值订单({}) payOrder({}) 不存在,请进行处理!]", + recharge.getId(), payOrderId); + throw exception(PAY_ORDER_NOT_FOUND); + } + + // 2.1 校验支付单已支付 + if (!PayOrderStatusEnum.isSuccess(payOrder.getStatus())) { + log.error("[validatePayOrderPaid][充值订单({}) payOrder({}) 未支付,请进行处理!payOrder 数据是:{}]", + recharge.getId(), payOrderId, toJsonString(payOrder)); + throw exception(WALLET_RECHARGE_UPDATE_PAID_PAY_ORDER_STATUS_NOT_SUCCESS); + } + // 2.2 校验支付金额一致 + if (notEqual(payOrder.getPrice(), recharge.getPayPrice())) { + log.error("[validatePayOrderPaid][充值订单({}) payOrder({}) 支付金额不匹配,请进行处理!钱包 数据是:{},payOrder 数据是:{}]", + recharge.getId(), payOrderId, toJsonString(recharge), toJsonString(payOrder)); + throw exception(WALLET_RECHARGE_UPDATE_PAID_PAY_PRICE_NOT_MATCH); + } + // 2.3 校验支付订单的商户订单匹配 + if (notEqual(payOrder.getMerchantOrderId(), recharge.getId().toString())) { + log.error("[validatePayOrderPaid][充值订单({}) 支付单不匹配({}),请进行处理!payOrder 数据是:{}]", + recharge.getId(), payOrderId, toJsonString(payOrder)); + throw exception(WALLET_RECHARGE_UPDATE_PAID_PAY_ORDER_ID_ERROR); + } + return payOrder; + } + + /** + * 获得自身的代理对象,解决 AOP 生效问题 + * + * @return 自己 + */ + private PayWalletRechargeServiceImpl getSelf() { + return SpringUtil.getBean(getClass()); + } + +} diff --git a/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/service/wallet/PayWalletService.java b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/service/wallet/PayWalletService.java new file mode 100644 index 0000000..6efdc92 --- /dev/null +++ b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/service/wallet/PayWalletService.java @@ -0,0 +1,99 @@ +package cn.iocoder.yudao.module.pay.service.wallet; + +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.module.pay.controller.admin.wallet.vo.wallet.PayWalletPageReqVO; +import cn.iocoder.yudao.module.pay.dal.dataobject.wallet.PayWalletDO; +import cn.iocoder.yudao.module.pay.dal.dataobject.wallet.PayWalletTransactionDO; +import cn.iocoder.yudao.module.pay.enums.wallet.PayWalletBizTypeEnum; + +/** + * 钱包 Service 接口 + * + * @author jason + */ +public interface PayWalletService { + + /** + * 获取钱包信息 + *

+ * 如果不存在,则创建钱包。由于用户注册时候不会创建钱包 + * + * @param userId 用户编号 + * @param userType 用户类型 + */ + PayWalletDO getOrCreateWallet(Long userId, Integer userType); + + /** + * 获取钱包信息 + * + * @param walletId 钱包 id + */ + PayWalletDO getWallet(Long walletId); + + /** + * 获得会员钱包分页 + * + * @param pageReqVO 分页查询 + * @return 会员钱包分页 + */ + PageResult getWalletPage(PayWalletPageReqVO pageReqVO); + + /** + * 钱包订单支付 + * + * @param walletId 钱包编号 + * @param outTradeNo 外部订单号 + * @param price 金额 + */ + PayWalletTransactionDO orderPay(Long walletId, String outTradeNo, Integer price); + + /** + * 钱包订单支付退款 + * + * @param outRefundNo 外部退款号 + * @param refundPrice 退款金额 + * @param reason 退款原因 + */ + PayWalletTransactionDO orderRefund(String outRefundNo, Integer refundPrice, String reason); + + /** + * 扣减钱包余额 + * + * @param walletId 钱包编号 + * @param bizId 业务关联编号 + * @param bizType 业务关联分类 + * @param price 扣减金额 + * @return 钱包流水 + */ + PayWalletTransactionDO reduceWalletBalance(Long walletId, Long bizId, + PayWalletBizTypeEnum bizType, Integer price); + + /** + * 增加钱包余额 + * + * @param walletId 钱包编号 + * @param bizId 业务关联编号 + * @param bizType 业务关联分类 + * @param price 增加金额 + * @return 钱包流水 + */ + PayWalletTransactionDO addWalletBalance(Long walletId, String bizId, + PayWalletBizTypeEnum bizType, Integer price); + + /** + * 冻结钱包部分余额 + * + * @param id 钱包编号 + * @param price 冻结金额 + */ + void freezePrice(Long id, Integer price); + + /** + * 解冻钱包余额 + * + * @param id 钱包编号 + * @param price 解冻金额 + */ + void unfreezePrice(Long id, Integer price); + +} diff --git a/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/service/wallet/PayWalletServiceImpl.java b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/service/wallet/PayWalletServiceImpl.java new file mode 100644 index 0000000..0005ace --- /dev/null +++ b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/service/wallet/PayWalletServiceImpl.java @@ -0,0 +1,233 @@ +package cn.iocoder.yudao.module.pay.service.wallet; + +import cn.hutool.core.lang.Assert; +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.common.util.date.DateUtils; +import cn.iocoder.yudao.module.pay.controller.admin.wallet.vo.wallet.PayWalletPageReqVO; +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.wallet.PayWalletDO; +import cn.iocoder.yudao.module.pay.dal.dataobject.wallet.PayWalletTransactionDO; +import cn.iocoder.yudao.module.pay.dal.mysql.wallet.PayWalletMapper; +import cn.iocoder.yudao.module.pay.dal.redis.wallet.PayWalletLockRedisDAO; +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.wallet.bo.WalletTransactionCreateReqBO; +import jakarta.annotation.Resource; +import lombok.SneakyThrows; +import lombok.extern.slf4j.Slf4j; +import org.springframework.context.annotation.Lazy; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.time.LocalDateTime; + +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; +import static cn.iocoder.yudao.module.pay.enums.ErrorCodeConstants.*; +import static cn.iocoder.yudao.module.pay.enums.wallet.PayWalletBizTypeEnum.PAYMENT; +import static cn.iocoder.yudao.module.pay.enums.wallet.PayWalletBizTypeEnum.PAYMENT_REFUND; + +/** + * 钱包 Service 实现类 + * + * @author jason + */ +@Service +@Slf4j +public class PayWalletServiceImpl implements PayWalletService { + + /** + * 通知超时时间,单位:毫秒 + */ + public static final long UPDATE_TIMEOUT_MILLIS = 120 * DateUtils.SECOND_MILLIS; + + @Resource + private PayWalletMapper walletMapper; + @Resource + private PayWalletLockRedisDAO lockRedisDAO; + + @Resource + @Lazy // 延迟加载,避免循环依赖 + private PayWalletTransactionService walletTransactionService; + @Resource + @Lazy // 延迟加载,避免循环依赖 + private PayOrderService orderService; + @Resource + @Lazy // 延迟加载,避免循环依赖 + private PayRefundService refundService; + + @Override + public PayWalletDO getOrCreateWallet(Long userId, Integer userType) { + PayWalletDO wallet = walletMapper.selectByUserIdAndType(userId, userType); + if (wallet == null) { + wallet = new PayWalletDO().setUserId(userId).setUserType(userType) + .setBalance(0).setTotalExpense(0).setTotalRecharge(0); + wallet.setCreateTime(LocalDateTime.now()); + walletMapper.insert(wallet); + } + return wallet; + } + + @Override + public PayWalletDO getWallet(Long walletId) { + return walletMapper.selectById(walletId); + } + + @Override + public PageResult getWalletPage(PayWalletPageReqVO pageReqVO) { + return walletMapper.selectPage(pageReqVO); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public PayWalletTransactionDO orderPay(Long walletId, String outTradeNo, Integer price) { + // 1. 判断支付交易拓展单是否存 + PayOrderExtensionDO orderExtension = orderService.getOrderExtensionByNo(outTradeNo); + if (orderExtension == null) { + throw exception(PAY_ORDER_EXTENSION_NOT_FOUND); + } + PayWalletDO wallet = walletMapper.selectById(walletId); + // 2. 扣减余额 + return reduceWalletBalance(wallet.getId(), orderExtension.getOrderId(), PAYMENT, price); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public PayWalletTransactionDO orderRefund(String outRefundNo, Integer refundPrice, String reason) { + // 1.1 判断退款单是否存在 + PayRefundDO payRefund = refundService.getRefundByNo(outRefundNo); + if (payRefund == null) { + throw exception(REFUND_NOT_FOUND); + } + // 1.2 校验是否可以退款 + Long walletId = validateWalletCanRefund(payRefund.getId(), payRefund.getChannelOrderNo()); + PayWalletDO wallet = walletMapper.selectById(walletId); + Assert.notNull(wallet, "钱包 {} 不存在", walletId); + + // 2. 增加余额 + return addWalletBalance(walletId, String.valueOf(payRefund.getId()), PAYMENT_REFUND, refundPrice); + } + + /** + * 校验是否能退款 + * + * @param refundId 支付退款单 id + * @param walletPayNo 钱包支付 no + */ + private Long validateWalletCanRefund(Long refundId, String walletPayNo) { + // 1. 校验钱包支付交易存在 + PayWalletTransactionDO walletTransaction = walletTransactionService.getWalletTransactionByNo(walletPayNo); + if (walletTransaction == null) { + throw exception(WALLET_TRANSACTION_NOT_FOUND); + } + // 2. 校验退款是否存在 + PayWalletTransactionDO refundTransaction = walletTransactionService.getWalletTransaction( + String.valueOf(refundId), PAYMENT_REFUND); + if (refundTransaction != null) { + throw exception(WALLET_REFUND_EXIST); + } + return walletTransaction.getWalletId(); + } + + @Override + @Transactional(rollbackFor = Exception.class) + @SneakyThrows + public PayWalletTransactionDO reduceWalletBalance(Long walletId, Long bizId, + PayWalletBizTypeEnum bizType, Integer price) { + // 1. 获取钱包 + PayWalletDO payWallet = getWallet(walletId); + if (payWallet == null) { + log.error("[reduceWalletBalance][用户钱包({})不存在]", walletId); + throw exception(WALLET_NOT_FOUND); + } + + // 2. 加锁,更新钱包余额(目的:避免钱包流水的并发更新时,余额变化不连贯) + return lockRedisDAO.lock(walletId, UPDATE_TIMEOUT_MILLIS, () -> { + // 2. 扣除余额 + int updateCounts; + switch (bizType) { + case PAYMENT: { + updateCounts = walletMapper.updateWhenConsumption(payWallet.getId(), price); + break; + } + case RECHARGE_REFUND: { + updateCounts = walletMapper.updateWhenRechargeRefund(payWallet.getId(), price); + break; + } + default: { + // TODO 其它类型待实现 + throw new UnsupportedOperationException("待实现"); + } + } + if (updateCounts == 0) { + throw exception(WALLET_BALANCE_NOT_ENOUGH); + } + + // 3. 生成钱包流水 + Integer afterBalance = payWallet.getBalance() - price; + WalletTransactionCreateReqBO bo = new WalletTransactionCreateReqBO().setWalletId(payWallet.getId()) + .setPrice(-price).setBalance(afterBalance).setBizId(String.valueOf(bizId)) + .setBizType(bizType.getType()).setTitle(bizType.getDescription()); + return walletTransactionService.createWalletTransaction(bo); + }); + } + + @Override + @Transactional(rollbackFor = Exception.class) + @SneakyThrows + public PayWalletTransactionDO addWalletBalance(Long walletId, String bizId, + PayWalletBizTypeEnum bizType, Integer price) { + // 1. 获取钱包 + PayWalletDO payWallet = getWallet(walletId); + if (payWallet == null) { + log.error("[addWalletBalance][用户钱包({})不存在]", walletId); + throw exception(WALLET_NOT_FOUND); + } + + // 2. 加锁,更新钱包余额(目的:避免钱包流水的并发更新时,余额变化不连贯) + return lockRedisDAO.lock(walletId, UPDATE_TIMEOUT_MILLIS, () -> { + // 3. 更新钱包金额 + switch (bizType) { + case PAYMENT_REFUND: { // 退款更新 + walletMapper.updateWhenConsumptionRefund(payWallet.getId(), price); + break; + } + case RECHARGE: { // 充值更新 + walletMapper.updateWhenRecharge(payWallet.getId(), price); + break; + } + case UPDATE_BALANCE: // 更新余额 + case TRANSFER: // 分佣提现 + walletMapper.updateWhenAdd(payWallet.getId(), price); + break; + default: { + throw new UnsupportedOperationException("待实现:" + bizType); + } + } + + // 4. 生成钱包流水 + WalletTransactionCreateReqBO transactionCreateReqBO = new WalletTransactionCreateReqBO() + .setWalletId(payWallet.getId()).setPrice(price).setBalance(payWallet.getBalance() + price) + .setBizId(bizId).setBizType(bizType.getType()).setTitle(bizType.getDescription()); + return walletTransactionService.createWalletTransaction(transactionCreateReqBO); + }); + } + + @Override + public void freezePrice(Long id, Integer price) { + int updateCounts = walletMapper.freezePrice(id, price); + if (updateCounts == 0) { + throw exception(WALLET_BALANCE_NOT_ENOUGH); + } + } + + @Override + public void unfreezePrice(Long id, Integer price) { + int updateCounts = walletMapper.unFreezePrice(id, price); + if (updateCounts == 0) { + throw exception(WALLET_FREEZE_PRICE_NOT_ENOUGH); + } + } + +} diff --git a/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/service/wallet/PayWalletTransactionService.java b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/service/wallet/PayWalletTransactionService.java new file mode 100644 index 0000000..af9c6a8 --- /dev/null +++ b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/service/wallet/PayWalletTransactionService.java @@ -0,0 +1,74 @@ +package cn.iocoder.yudao.module.pay.service.wallet; + +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.module.pay.controller.admin.wallet.vo.transaction.PayWalletTransactionPageReqVO; +import cn.iocoder.yudao.module.pay.controller.app.wallet.vo.transaction.AppPayWalletTransactionPageReqVO; +import cn.iocoder.yudao.module.pay.controller.app.wallet.vo.transaction.AppPayWalletTransactionSummaryRespVO; +import cn.iocoder.yudao.module.pay.dal.dataobject.wallet.PayWalletTransactionDO; +import cn.iocoder.yudao.module.pay.enums.wallet.PayWalletBizTypeEnum; +import cn.iocoder.yudao.module.pay.service.wallet.bo.WalletTransactionCreateReqBO; + +import jakarta.validation.Valid; + +import java.time.LocalDateTime; + +/** + * 钱包余额流水 Service 接口 + * + * @author jason + */ +public interface PayWalletTransactionService { + + /** + * 查询钱包余额流水分页 + * + * @param userId 用户编号 + * @param userType 用户类型 + * @param pageVO 分页查询参数 + */ + PageResult getWalletTransactionPage(Long userId, Integer userType, + AppPayWalletTransactionPageReqVO pageVO); + + /** + * 查询钱包余额流水分页 + * + * @param pageVO 分页查询参数 + */ + PageResult getWalletTransactionPage(PayWalletTransactionPageReqVO pageVO); + + /** + * 新增钱包余额流水 + * + * @param bo 创建钱包流水 bo + * @return 新建的钱包 do + */ + PayWalletTransactionDO createWalletTransaction(@Valid WalletTransactionCreateReqBO bo); + + /** + * 根据 no,获取钱包余流水 + * + * @param no 流水号 + */ + PayWalletTransactionDO getWalletTransactionByNo(String no); + + /** + * 获取钱包流水 + * + * @param bizId 业务编号 + * @param type 业务类型 + * @return 钱包流水 + */ + PayWalletTransactionDO getWalletTransaction(String bizId, PayWalletBizTypeEnum type); + + /** + * 获得钱包流水统计 + * + * @param userId 用户编号 + * @param userType 用户类型 + * @param createTime 时间段 + * @return 钱包流水统计 + */ + AppPayWalletTransactionSummaryRespVO getWalletTransactionSummary(Long userId, Integer userType, + LocalDateTime[] createTime); + +} diff --git a/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/service/wallet/PayWalletTransactionServiceImpl.java b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/service/wallet/PayWalletTransactionServiceImpl.java new file mode 100644 index 0000000..4c01cd8 --- /dev/null +++ b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/service/wallet/PayWalletTransactionServiceImpl.java @@ -0,0 +1,95 @@ +package cn.iocoder.yudao.module.pay.service.wallet; + +import cn.hutool.core.util.ObjectUtil; +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.module.pay.controller.admin.wallet.vo.transaction.PayWalletTransactionPageReqVO; +import cn.iocoder.yudao.module.pay.controller.app.wallet.vo.transaction.AppPayWalletTransactionPageReqVO; +import cn.iocoder.yudao.module.pay.controller.app.wallet.vo.transaction.AppPayWalletTransactionSummaryRespVO; +import cn.iocoder.yudao.module.pay.convert.wallet.PayWalletTransactionConvert; +import cn.iocoder.yudao.module.pay.dal.dataobject.wallet.PayWalletDO; +import cn.iocoder.yudao.module.pay.dal.dataobject.wallet.PayWalletTransactionDO; +import cn.iocoder.yudao.module.pay.dal.mysql.wallet.PayWalletTransactionMapper; +import cn.iocoder.yudao.module.pay.dal.redis.no.PayNoRedisDAO; +import cn.iocoder.yudao.module.pay.enums.wallet.PayWalletBizTypeEnum; +import cn.iocoder.yudao.module.pay.service.wallet.bo.WalletTransactionCreateReqBO; +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.validation.annotation.Validated; + +import java.time.LocalDateTime; + +import static cn.iocoder.yudao.module.pay.controller.app.wallet.vo.transaction.AppPayWalletTransactionPageReqVO.TYPE_EXPENSE; +import static cn.iocoder.yudao.module.pay.controller.app.wallet.vo.transaction.AppPayWalletTransactionPageReqVO.TYPE_INCOME; + +/** + * 钱包流水 Service 实现类 + * + * @author jason + */ +@Service +@Slf4j +@Validated +public class PayWalletTransactionServiceImpl implements PayWalletTransactionService { + + /** + * 钱包流水的 no 前缀 + */ + private static final String WALLET_NO_PREFIX = "W"; + + @Resource + private PayWalletService payWalletService; + @Resource + private PayWalletTransactionMapper payWalletTransactionMapper; + @Resource + private PayNoRedisDAO noRedisDAO; + + @Override + public PageResult getWalletTransactionPage(Long userId, Integer userType, + AppPayWalletTransactionPageReqVO pageVO) { + PayWalletDO wallet = payWalletService.getOrCreateWallet(userId, userType); + return payWalletTransactionMapper.selectPage(wallet.getId(), pageVO.getType(), pageVO, pageVO.getCreateTime()); + } + + @Override + public PageResult getWalletTransactionPage(PayWalletTransactionPageReqVO pageVO) { + // 基于 userId + userType 查询钱包 + if (pageVO.getWalletId() == null + && ObjectUtil.isAllNotEmpty(pageVO.getUserId(), pageVO.getUserType())) { + PayWalletDO wallet = payWalletService.getOrCreateWallet(pageVO.getUserId(), pageVO.getUserType()); + if (wallet != null) { + pageVO.setWalletId(wallet.getId()); + } + } + + // 查询分页 + return payWalletTransactionMapper.selectPage(pageVO.getWalletId(), null, pageVO, null); + } + + @Override + public PayWalletTransactionDO createWalletTransaction(WalletTransactionCreateReqBO bo) { + PayWalletTransactionDO transaction = PayWalletTransactionConvert.INSTANCE.convert(bo) + .setNo(noRedisDAO.generate(WALLET_NO_PREFIX)); + payWalletTransactionMapper.insert(transaction); + return transaction; + } + + @Override + public PayWalletTransactionDO getWalletTransactionByNo(String no) { + return payWalletTransactionMapper.selectByNo(no); + } + + @Override + public PayWalletTransactionDO getWalletTransaction(String bizId, PayWalletBizTypeEnum type) { + return payWalletTransactionMapper.selectByBiz(bizId, type.getType()); + } + + @Override + public AppPayWalletTransactionSummaryRespVO getWalletTransactionSummary(Long userId, Integer userType, LocalDateTime[] createTime) { + PayWalletDO wallet = payWalletService.getOrCreateWallet(userId, userType); + return new AppPayWalletTransactionSummaryRespVO() + .setTotalExpense(payWalletTransactionMapper.selectPriceSum(wallet.getId(), TYPE_EXPENSE, createTime)) + .setTotalIncome(payWalletTransactionMapper.selectPriceSum(wallet.getId(), TYPE_INCOME, createTime)); + } + +} diff --git a/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/service/wallet/bo/WalletTransactionCreateReqBO.java b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/service/wallet/bo/WalletTransactionCreateReqBO.java new file mode 100644 index 0000000..cde57f1 --- /dev/null +++ b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/service/wallet/bo/WalletTransactionCreateReqBO.java @@ -0,0 +1,59 @@ +package cn.iocoder.yudao.module.pay.service.wallet.bo; + +import cn.iocoder.yudao.framework.common.validation.InEnum; +import cn.iocoder.yudao.module.pay.enums.wallet.PayWalletBizTypeEnum; +import lombok.Data; + +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; + +/** + * 创建钱包流水 BO + * + * @author jason + */ +@Data +public class WalletTransactionCreateReqBO { + + /** + * 钱包编号 + * + */ + @NotNull(message = "钱包编号不能为空") + private Long walletId; + + /** + * 交易金额,单位分 + * + * 正值表示余额增加,负值表示余额减少 + */ + @NotNull(message = "交易金额不能为空") + private Integer price; + + /** + * 交易后余额,单位分 + */ + @NotNull(message = "交易后余额不能为空") + private Integer balance; + + /** + * 关联业务分类 + * + * 枚举 {@link PayWalletBizTypeEnum#getType()} + */ + @NotNull(message = "关联业务分类不能为空") + @InEnum(PayWalletBizTypeEnum.class) + private Integer bizType; + + /** + * 关联业务编号 + */ + @NotEmpty(message = "关联业务编号不能为空") + private String bizId; + + /** + * 流水说明 + */ + @NotEmpty(message = "流水说明不能为空") + private String title; +} diff --git a/yudao-module-pay/src/test/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/impl/PayClientFactoryImplIntegrationTest.java b/yudao-module-pay/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/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/src/test/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/impl/alipay/AbstractAlipayClientTest.java b/yudao-module-pay/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/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/src/test/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/impl/alipay/AlipayBarPayClientTest.java b/yudao-module-pay/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/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/src/test/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/impl/alipay/AlipayPcPayClientTest.java b/yudao-module-pay/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/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/src/test/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/impl/alipay/AlipayQrPayClientTest.java b/yudao-module-pay/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/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/src/test/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/impl/alipay/AlipayWapPayClientTest.java b/yudao-module-pay/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/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/src/test/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/impl/weixin/WxBarPayClientIntegrationTest.java b/yudao-module-pay/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/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/src/test/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/impl/weixin/WxNativePayClientIntegrationTest.java b/yudao-module-pay/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/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/src/test/java/cn/iocoder/yudao/module/pay/service/app/PayAppServiceTest.java b/yudao-module-pay/src/test/java/cn/iocoder/yudao/module/pay/service/app/PayAppServiceTest.java new file mode 100644 index 0000000..326d586 --- /dev/null +++ b/yudao-module-pay/src/test/java/cn/iocoder/yudao/module/pay/service/app/PayAppServiceTest.java @@ -0,0 +1,260 @@ +package cn.iocoder.yudao.module.pay.service.app; + +import cn.hutool.core.util.RandomUtil; +import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest; +import cn.iocoder.yudao.module.pay.controller.admin.app.vo.PayAppCreateReqVO; +import cn.iocoder.yudao.module.pay.controller.admin.app.vo.PayAppPageReqVO; +import cn.iocoder.yudao.module.pay.controller.admin.app.vo.PayAppUpdateReqVO; +import cn.iocoder.yudao.module.pay.dal.dataobject.app.PayAppDO; +import cn.iocoder.yudao.module.pay.dal.mysql.app.PayAppMapper; +import cn.iocoder.yudao.module.pay.service.order.PayOrderService; +import cn.iocoder.yudao.module.pay.service.refund.PayRefundService; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.context.annotation.Import; + +import jakarta.annotation.Resource; +import java.util.Map; + +import static cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.buildBetweenTime; +import static cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.buildTime; +import static cn.iocoder.yudao.framework.common.util.object.ObjectUtils.cloneIgnoreId; +import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertPojoEquals; +import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertServiceException; +import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.*; +import static cn.iocoder.yudao.module.pay.enums.ErrorCodeConstants.*; +import static java.util.Collections.singleton; +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.when; + +/** + * {@link PayAppServiceImpl} 的单元测试 + * + * @author aquan + */ +@Disabled // TODO 芋艿:后续 fix 补充的单测 +@Import(PayAppServiceImpl.class) +public class PayAppServiceTest extends BaseDbUnitTest { + + @Resource + private PayAppServiceImpl appService; + + @Resource + private PayAppMapper appMapper; + + @MockBean + private PayOrderService orderService; + @MockBean + private PayRefundService refundService; + + @Test + public void testCreateApp_success() { + // 准备参数 + PayAppCreateReqVO reqVO = randomPojo(PayAppCreateReqVO.class, o -> + o.setStatus((RandomUtil.randomEle(CommonStatusEnum.values()).getStatus())) + .setOrderNotifyUrl(randomURL()) + .setRefundNotifyUrl(randomURL())); + + // 调用 + Long appId = appService.createApp(reqVO); + // 断言 + assertNotNull(appId); + PayAppDO app = appMapper.selectById(appId); + assertPojoEquals(reqVO, app); + } + + @Test + public void testUpdateApp_success() { + // mock 数据 + PayAppDO dbApp = randomPojo(PayAppDO.class); + appMapper.insert(dbApp);// @Sql: 先插入出一条存在的数据 + // 准备参数 + PayAppUpdateReqVO reqVO = randomPojo(PayAppUpdateReqVO.class, o -> { + o.setStatus(CommonStatusEnum.ENABLE.getStatus()); + o.setOrderNotifyUrl(randomURL()).setRefundNotifyUrl(randomURL()); + o.setId(dbApp.getId()); // 设置更新的 ID + }); + + // 调用 + appService.updateApp(reqVO); + // 校验是否更新正确 + PayAppDO app = appMapper.selectById(reqVO.getId()); // 获取最新的 + assertPojoEquals(reqVO, app); + } + + @Test + public void testUpdateApp_notExists() { + // 准备参数 + PayAppUpdateReqVO reqVO = randomPojo(PayAppUpdateReqVO.class, o -> + o.setStatus((RandomUtil.randomEle(CommonStatusEnum.values()).getStatus()))); + // 调用, 并断言异常 + assertServiceException(() -> appService.updateApp(reqVO), APP_NOT_FOUND); + } + + @Test + public void testUpdateAppStatus() { + // mock 数据 + PayAppDO dbApp = randomPojo(PayAppDO.class, o -> + o.setStatus(CommonStatusEnum.DISABLE.getStatus())); + appMapper.insert(dbApp);// @Sql: 先插入出一条存在的数据 + + // 准备参数 + Long id = dbApp.getId(); + Integer status = CommonStatusEnum.ENABLE.getStatus(); + // 调用 + appService.updateAppStatus(id, status); + // 断言 + PayAppDO app = appMapper.selectById(id); // 获取最新的 + assertEquals(status, app.getStatus()); + } + + @Test + public void testDeleteApp_success() { + // mock 数据 + PayAppDO dbApp = randomPojo(PayAppDO.class); + appMapper.insert(dbApp);// @Sql: 先插入出一条存在的数据 + // 准备参数 + Long id = dbApp.getId(); + + // 调用 + appService.deleteApp(id); + // 校验数据不存在了 + assertNull(appMapper.selectById(id)); + } + + @Test + public void testDeleteApp_notExists() { + // 准备参数 + Long id = randomLongId(); + + // 调用, 并断言异常 + assertServiceException(() -> appService.deleteApp(id), APP_NOT_FOUND); + } + + @Test + public void testDeleteApp_existOrder() { + // mock 数据 + PayAppDO dbApp = randomPojo(PayAppDO.class); + appMapper.insert(dbApp);// @Sql: 先插入出一条存在的数据 + // 准备参数 + Long id = dbApp.getId(); + // mock 订单有订单 + when(orderService.getOrderCountByAppId(eq(id))).thenReturn(10L); + + // 调用, 并断言异常 + assertServiceException(() -> appService.deleteApp(id), APP_EXIST_ORDER_CANT_DELETE); + } + + @Test + public void testDeleteApp_existRefund() { + // mock 数据 + PayAppDO dbApp = randomPojo(PayAppDO.class); + appMapper.insert(dbApp);// @Sql: 先插入出一条存在的数据 + // 准备参数 + Long id = dbApp.getId(); + // mock 订单有订单 + when(refundService.getRefundCountByAppId(eq(id))).thenReturn(10L); + + // 调用, 并断言异常 + assertServiceException(() -> appService.deleteApp(id), APP_EXIST_REFUND_CANT_DELETE); + } + + @Test + public void testApp() { + // mock 数据 + PayAppDO dbApp = randomPojo(PayAppDO.class); + appMapper.insert(dbApp);// @Sql: 先插入出一条存在的数据 + // 准备参数 + Long id = dbApp.getId(); + + // 调用 + PayAppDO app = appService.getApp(id); + // 校验数据一致 + assertPojoEquals(app, dbApp); + } + + @Test + public void testAppMap() { + // mock 数据 + PayAppDO dbApp01 = randomPojo(PayAppDO.class); + appMapper.insert(dbApp01);// @Sql: 先插入出一条存在的数据 + PayAppDO dbApp02 = randomPojo(PayAppDO.class); + appMapper.insert(dbApp02);// @Sql: 先插入出一条存在的数据 + // 准备参数 + Long id = dbApp01.getId(); + + // 调用 + Map appMap = appService.getAppMap(singleton(id)); + // 校验数据一致 + assertEquals(1, appMap.size()); + assertPojoEquals(dbApp01, appMap.get(id)); + } + + @Test + public void testGetAppPage() { + // mock 数据 + PayAppDO dbApp = randomPojo(PayAppDO.class, o -> { // 等会查询到 + o.setName("灿灿姐的杂货铺"); + o.setStatus(CommonStatusEnum.ENABLE.getStatus()); + o.setCreateTime(buildTime(2021,11,20)); + }); + + appMapper.insert(dbApp); + // 测试 name 不匹配 + appMapper.insert(cloneIgnoreId(dbApp, o -> o.setName("敏敏姐的杂货铺"))); + // 测试 status 不匹配 + appMapper.insert(cloneIgnoreId(dbApp, o -> o.setStatus(CommonStatusEnum.DISABLE.getStatus()))); + // 测试 createTime 不匹配 + appMapper.insert(cloneIgnoreId(dbApp, o -> o.setCreateTime(buildTime(2021,12,21)))); + // 准备参数 + PayAppPageReqVO reqVO = new PayAppPageReqVO(); + reqVO.setName("灿灿姐的杂货铺"); + reqVO.setStatus(CommonStatusEnum.ENABLE.getStatus()); + reqVO.setCreateTime(buildBetweenTime(2021, 11, 19, 2021, 11, 21)); + + // 调用 + PageResult pageResult = appService.getAppPage(reqVO); + // 断言 + assertEquals(1, pageResult.getTotal()); + assertEquals(1, pageResult.getList().size()); + assertPojoEquals(dbApp, pageResult.getList().get(0)); + } + + @Test + public void testValidPayApp_success() { + // mock 数据 + PayAppDO dbApp = randomPojo(PayAppDO.class, + o -> o.setStatus(CommonStatusEnum.ENABLE.getStatus())); + appMapper.insert(dbApp);// @Sql: 先插入出一条存在的数据 + // 准备参数 + Long id = dbApp.getId(); + + // 调用 + PayAppDO app = appService.validPayApp(id); + // 校验数据一致 + assertPojoEquals(app, dbApp); + } + + @Test + public void testValidPayApp_notFound() { + assertServiceException(() -> appService.validPayApp(randomLongId()), APP_NOT_FOUND); + } + + @Test + public void testValidPayApp_disable() { + // mock 数据 + PayAppDO dbApp = randomPojo(PayAppDO.class, + o -> o.setStatus(CommonStatusEnum.DISABLE.getStatus())); + appMapper.insert(dbApp);// @Sql: 先插入出一条存在的数据 + // 准备参数 + Long id = dbApp.getId(); + + // 调用,并断言异常 + assertServiceException(() -> appService.validPayApp(id), APP_IS_DISABLE); + } + +} diff --git a/yudao-module-pay/src/test/java/cn/iocoder/yudao/module/pay/service/channel/PayChannelServiceTest.java b/yudao-module-pay/src/test/java/cn/iocoder/yudao/module/pay/service/channel/PayChannelServiceTest.java new file mode 100644 index 0000000..4e05b87 --- /dev/null +++ b/yudao-module-pay/src/test/java/cn/iocoder/yudao/module/pay/service/channel/PayChannelServiceTest.java @@ -0,0 +1,337 @@ +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.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; +import cn.iocoder.yudao.module.pay.dal.dataobject.channel.PayChannelDO; +import cn.iocoder.yudao.module.pay.dal.mysql.channel.PayChannelMapper; +import com.alibaba.fastjson.JSON; +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.context.annotation.Import; + +import jakarta.annotation.Resource; +import jakarta.validation.Validator; +import java.util.Collections; +import java.util.List; + +import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertPojoEquals; +import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertServiceException; +import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.*; +import static cn.iocoder.yudao.module.pay.enums.ErrorCodeConstants.*; +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.*; + +@Import({PayChannelServiceImpl.class}) +public class PayChannelServiceTest extends BaseDbUnitTest { + + @Resource + private PayChannelServiceImpl channelService; + + @Resource + private PayChannelMapper channelMapper; + + @MockBean + private PayClientFactory payClientFactory; + @MockBean + private Validator validator; + + @Test + public void testCreateChannel_success() { + // 准备参数 + WxPayClientConfig config = randomWxPayClientConfig(); + PayChannelCreateReqVO reqVO = randomPojo(PayChannelCreateReqVO.class, o -> { + o.setStatus(randomCommonStatus()); + o.setCode(PayChannelEnum.WX_PUB.getCode()); + o.setConfig(JsonUtils.toJsonString(config)); + }); + + // 调用 + Long channelId = channelService.createChannel(reqVO); + // 校验记录的属性是否正确 + PayChannelDO channel = channelMapper.selectById(channelId); + assertPojoEquals(reqVO, channel, "config"); + assertPojoEquals(config, channel.getConfig()); + } + + @Test + public void testCreateChannel_exists() { + // mock 数据 + PayChannelDO dbChannel = randomPojo(PayChannelDO.class, + o -> o.setConfig(randomWxPayClientConfig())); + channelMapper.insert(dbChannel);// @Sql: 先插入出一条存在的数据 + // 准备参数 + PayChannelCreateReqVO reqVO = randomPojo(PayChannelCreateReqVO.class, o -> { + o.setAppId(dbChannel.getAppId()); + o.setCode(dbChannel.getCode()); + }); + + // 调用, 并断言异常 + assertServiceException(() -> channelService.createChannel(reqVO), CHANNEL_EXIST_SAME_CHANNEL_ERROR); + } + + @Test + public void testUpdateChannel_success() { + // mock 数据 + PayChannelDO dbChannel = randomPojo(PayChannelDO.class, o -> { + o.setCode(PayChannelEnum.ALIPAY_APP.getCode()); + o.setConfig(randomAlipayPayClientConfig()); + }); + channelMapper.insert(dbChannel);// @Sql: 先插入出一条存在的数据 + // 准备参数 + AlipayPayClientConfig config = randomAlipayPayClientConfig(); + PayChannelUpdateReqVO reqVO = randomPojo(PayChannelUpdateReqVO.class, o -> { + o.setId(dbChannel.getId()); // 设置更新的 ID + o.setStatus(randomCommonStatus()); + o.setConfig(JsonUtils.toJsonString(config)); + }); + + // 调用 + channelService.updateChannel(reqVO); + // 校验是否更新正确 + PayChannelDO channel = channelMapper.selectById(reqVO.getId()); // 获取最新的 + assertPojoEquals(reqVO, channel, "config"); + assertPojoEquals(config, channel.getConfig()); + } + + @Test + public void testUpdateChannel_notExists() { + // 准备参数 + AlipayPayClientConfig payClientPublicKeyConfig = randomAlipayPayClientConfig(); + PayChannelUpdateReqVO reqVO = randomPojo(PayChannelUpdateReqVO.class, o -> { + o.setStatus(CommonStatusEnum.ENABLE.getStatus()); + o.setConfig(JSON.toJSONString(payClientPublicKeyConfig)); + }); + + // 调用, 并断言异常 + assertServiceException(() -> channelService.updateChannel(reqVO), CHANNEL_NOT_FOUND); + } + + @Test + public void testDeleteChannel_success() { + // mock 数据 + PayChannelDO dbChannel = randomPojo(PayChannelDO.class, o -> { + o.setCode(PayChannelEnum.ALIPAY_APP.getCode()); + o.setConfig(randomAlipayPayClientConfig()); + }); + channelMapper.insert(dbChannel);// @Sql: 先插入出一条存在的数据 + // 准备参数 + Long id = dbChannel.getId(); + + // 调用 + channelService.deleteChannel(id); + // 校验数据不存在了 + assertNull(channelMapper.selectById(id)); + } + + @Test + public void testDeleteChannel_notExists() { + // 准备参数 + Long id = randomLongId(); + + // 调用, 并断言异常 + assertServiceException(() -> channelService.deleteChannel(id), CHANNEL_NOT_FOUND); + } + + @Test + public void testGetChannel() { + // mock 数据 + PayChannelDO dbChannel = randomPojo(PayChannelDO.class, o -> { + o.setCode(PayChannelEnum.ALIPAY_APP.getCode()); + o.setConfig(randomAlipayPayClientConfig()); + }); + channelMapper.insert(dbChannel);// @Sql: 先插入出一条存在的数据 + // 准备参数 + Long id = dbChannel.getId(); + + // 调用 + PayChannelDO channel = channelService.getChannel(id); + // 校验是否更新正确 + assertPojoEquals(dbChannel, channel); + } + + @Test + public void testGetChannelListByAppIds() { + // mock 数据 + PayChannelDO dbChannel01 = randomPojo(PayChannelDO.class, o -> { + o.setCode(PayChannelEnum.ALIPAY_APP.getCode()); + o.setConfig(randomAlipayPayClientConfig()); + }); + channelMapper.insert(dbChannel01);// @Sql: 先插入出一条存在的数据 + PayChannelDO dbChannel02 = randomPojo(PayChannelDO.class, o -> { + o.setCode(PayChannelEnum.WX_PUB.getCode()); + o.setConfig(randomWxPayClientConfig()); + }); + channelMapper.insert(dbChannel02);// @Sql: 先插入出一条存在的数据 + // 准备参数 + Long appId = dbChannel01.getAppId(); + + // 调用 + List channels = channelService.getChannelListByAppIds(Collections.singleton(appId)); + // 校验是否更新正确 + assertEquals(1, channels.size()); + assertPojoEquals(dbChannel01, channels.get(0)); + } + + @Test + public void testGetChannelByAppIdAndCode() { + // mock 数据 + PayChannelDO dbChannel = randomPojo(PayChannelDO.class, o -> { + o.setCode(PayChannelEnum.ALIPAY_APP.getCode()); + o.setConfig(randomAlipayPayClientConfig()); + }); + channelMapper.insert(dbChannel);// @Sql: 先插入出一条存在的数据 + // 准备参数 + Long appId = dbChannel.getAppId(); + String code = dbChannel.getCode(); + + // 调用 + PayChannelDO channel = channelService.getChannelByAppIdAndCode(appId, code); + // 断言 + assertPojoEquals(channel, dbChannel); + } + + @Test + public void testValidPayChannel_notExists() { + // 准备参数 + Long id = randomLongId(); + + // 调用, 并断言异常 + assertServiceException(() -> channelService.validPayChannel(id), CHANNEL_NOT_FOUND); + } + + @Test + public void testValidPayChannel_isDisable() { + // mock 数据 + PayChannelDO dbChannel = randomPojo(PayChannelDO.class, o -> { + o.setCode(PayChannelEnum.ALIPAY_APP.getCode()); + o.setConfig(randomAlipayPayClientConfig()); + o.setStatus(CommonStatusEnum.DISABLE.getStatus()); + }); + channelMapper.insert(dbChannel);// @Sql: 先插入出一条存在的数据 + // 准备参数 + Long id = dbChannel.getId(); + + // 调用, 并断言异常 + assertServiceException(() -> channelService.validPayChannel(id), CHANNEL_IS_DISABLE); + } + + @Test + public void testValidPayChannel_success() { + // mock 数据 + PayChannelDO dbChannel = randomPojo(PayChannelDO.class, o -> { + o.setCode(PayChannelEnum.ALIPAY_APP.getCode()); + o.setConfig(randomAlipayPayClientConfig()); + o.setStatus(CommonStatusEnum.ENABLE.getStatus()); + }); + channelMapper.insert(dbChannel);// @Sql: 先插入出一条存在的数据 + // 准备参数 + Long id = dbChannel.getId(); + + // 调用 + PayChannelDO channel = channelService.validPayChannel(id); + // 断言异常 + assertPojoEquals(channel, dbChannel); + } + + @Test + public void testValidPayChannel_appIdAndCode() { + // mock 数据 + PayChannelDO dbChannel = randomPojo(PayChannelDO.class, o -> { + o.setCode(PayChannelEnum.ALIPAY_APP.getCode()); + o.setConfig(randomAlipayPayClientConfig()); + o.setStatus(CommonStatusEnum.ENABLE.getStatus()); + }); + channelMapper.insert(dbChannel);// @Sql: 先插入出一条存在的数据 + // 准备参数 + Long appId = dbChannel.getAppId(); + String code = dbChannel.getCode(); + + // 调用 + PayChannelDO channel = channelService.validPayChannel(appId, code); + // 断言异常 + assertPojoEquals(channel, dbChannel); + } + + @Test + public void testGetEnableChannelList() { + // 准备参数 + Long appId = randomLongId(); + // mock 数据 01(enable 不匹配) + PayChannelDO dbChannel01 = randomPojo(PayChannelDO.class, o -> { + o.setCode(PayChannelEnum.ALIPAY_APP.getCode()); + o.setConfig(randomAlipayPayClientConfig()); + o.setStatus(CommonStatusEnum.DISABLE.getStatus()); + }); + channelMapper.insert(dbChannel01);// @Sql: 先插入出一条存在的数据 + // mock 数据 02(appId 不匹配) + PayChannelDO dbChannel02 = randomPojo(PayChannelDO.class, o -> { + o.setCode(PayChannelEnum.ALIPAY_APP.getCode()); + o.setConfig(randomAlipayPayClientConfig()); + o.setStatus(CommonStatusEnum.ENABLE.getStatus()); + }); + channelMapper.insert(dbChannel02);// @Sql: 先插入出一条存在的数据 + // mock 数据 03 + PayChannelDO dbChannel03 = randomPojo(PayChannelDO.class, o -> { + o.setCode(PayChannelEnum.ALIPAY_APP.getCode()); + o.setConfig(randomAlipayPayClientConfig()); + o.setAppId(appId); + o.setStatus(CommonStatusEnum.ENABLE.getStatus()); + }); + channelMapper.insert(dbChannel03);// @Sql: 先插入出一条存在的数据 + + // 调用 + List channel = channelService.getEnableChannelList(appId); + // 断言异常 + assertPojoEquals(channel, dbChannel03); + } + + @Test + public void testGetPayClient() { + // mock 数据 + PayChannelDO channel = randomPojo(PayChannelDO.class, o -> { + o.setCode(PayChannelEnum.ALIPAY_APP.getCode()); + o.setConfig(randomAlipayPayClientConfig()); + o.setStatus(CommonStatusEnum.ENABLE.getStatus()); + }); + channelMapper.insert(channel); + // mock 参数 + Long id = channel.getId(); + // mock 方法 + PayClient mockClient = mock(PayClient.class); + when(payClientFactory.createOrUpdatePayClient(eq(id), eq(channel.getCode()), eq(channel.getConfig()))) + .thenReturn(mockClient); + + // 调用 + PayClient client = channelService.getPayClient(id); + // 断言 + assertSame(client, mockClient); + } + + public WxPayClientConfig randomWxPayClientConfig() { + return new WxPayClientConfig() + .setAppId(randomString()) + .setMchId(randomString()) + .setApiVersion(WxPayClientConfig.API_VERSION_V2) + .setMchKey(randomString()); + } + + public AlipayPayClientConfig randomAlipayPayClientConfig() { + return new AlipayPayClientConfig() + .setServerUrl(randomURL()) + .setAppId(randomString()) + .setSignType(AlipayPayClientConfig.SIGN_TYPE_DEFAULT) + .setMode(AlipayPayClientConfig.MODE_PUBLIC_KEY) + .setPrivateKey(randomString()) + .setAlipayPublicKey(randomString()); + } + +} diff --git a/yudao-module-pay/src/test/java/cn/iocoder/yudao/module/pay/service/notify/PayNotifyServiceTest.java b/yudao-module-pay/src/test/java/cn/iocoder/yudao/module/pay/service/notify/PayNotifyServiceTest.java new file mode 100644 index 0000000..cfd01ae --- /dev/null +++ b/yudao-module-pay/src/test/java/cn/iocoder/yudao/module/pay/service/notify/PayNotifyServiceTest.java @@ -0,0 +1,353 @@ +package cn.iocoder.yudao.module.pay.service.notify; + +import cn.hutool.extra.spring.SpringUtil; +import cn.iocoder.yudao.framework.common.pojo.CommonResult; +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest; +import cn.iocoder.yudao.module.pay.controller.admin.notify.vo.PayNotifyTaskPageReqVO; +import cn.iocoder.yudao.module.pay.dal.dataobject.notify.PayNotifyLogDO; +import cn.iocoder.yudao.module.pay.dal.dataobject.notify.PayNotifyTaskDO; +import cn.iocoder.yudao.module.pay.dal.dataobject.order.PayOrderDO; +import cn.iocoder.yudao.module.pay.dal.dataobject.refund.PayRefundDO; +import cn.iocoder.yudao.module.pay.dal.mysql.notify.PayNotifyLogMapper; +import cn.iocoder.yudao.module.pay.dal.mysql.notify.PayNotifyTaskMapper; +import cn.iocoder.yudao.module.pay.dal.redis.notify.PayNotifyLockRedisDAO; +import cn.iocoder.yudao.module.pay.enums.notify.PayNotifyStatusEnum; +import cn.iocoder.yudao.module.pay.enums.notify.PayNotifyTypeEnum; +import cn.iocoder.yudao.module.pay.framework.job.config.PayJobConfiguration; +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.refund.PayRefundServiceImpl; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; +import org.mockito.MockedStatic; +import org.redisson.api.RLock; +import org.redisson.api.RedissonClient; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.context.annotation.Import; + +import jakarta.annotation.Resource; +import java.time.Duration; +import java.util.List; + +import static cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants.BAD_REQUEST; +import static cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.*; +import static cn.iocoder.yudao.framework.common.util.object.ObjectUtils.cloneIgnoreId; +import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertPojoEquals; +import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo; +import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomString; +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.*; + +/** + * {@link PayRefundServiceImpl} 的单元测试类 + * + * @author 芋艿 + */ +@Disabled // TODO 芋艿:后续 fix 补充的单测 +@Import({PayJobConfiguration.class, PayNotifyServiceImpl.class, PayNotifyLockRedisDAO.class}) +public class PayNotifyServiceTest extends BaseDbUnitTest { + + @Resource + private PayNotifyServiceImpl notifyService; + + @MockBean + private PayOrderService orderService; + @MockBean + private PayRefundService refundService; + + @Resource + private PayNotifyTaskMapper notifyTaskMapper; + @Resource + private PayNotifyLogMapper notifyLogMapper; + + @MockBean + private RedissonClient redissonClient; + + @Test + public void testCreatePayNotifyTask_order() { + PayNotifyServiceImpl payNotifyService = mock(PayNotifyServiceImpl.class); + try (MockedStatic springUtilMockedStatic = mockStatic(SpringUtil.class)) { + springUtilMockedStatic.when(() -> SpringUtil.getBean(eq(PayNotifyServiceImpl.class))) + .thenReturn(payNotifyService); + + // 准备参数 + Integer type = PayNotifyTypeEnum.ORDER.getType(); + Long dataId = 1L; + // mock 方法(order) + PayOrderDO order = randomPojo(PayOrderDO.class); + when(orderService.getOrder(eq(1L))).thenReturn(order); + // mock 方法(lock) + mockLock(null); // null 的原因,是咱没办法拿到 taskId 新增 + + // 调用 + notifyService.createPayNotifyTask(type, dataId); + // 断言,task + PayNotifyTaskDO dbTask = notifyTaskMapper.selectOne(null); + assertNotNull(dbTask.getNextNotifyTime()); + assertThat(dbTask) + .extracting("type", "dataId", "status", "notifyTimes", "maxNotifyTimes", + "appId", "merchantOrderId", "notifyUrl") + .containsExactly(type, dataId, PayNotifyStatusEnum.WAITING.getStatus(), 0, 9, + order.getAppId(), order.getMerchantOrderId(), order.getNotifyUrl()); + // 断言,调用 + verify(payNotifyService).executeNotify0(eq(dbTask)); + } + } + + @Test + public void testCreatePayNotifyTask_refund() { + PayNotifyServiceImpl payNotifyService = mock(PayNotifyServiceImpl.class); + try (MockedStatic springUtilMockedStatic = mockStatic(SpringUtil.class)) { + springUtilMockedStatic.when(() -> SpringUtil.getBean(eq(PayNotifyServiceImpl.class))) + .thenReturn(payNotifyService); + + // 准备参数 + Integer type = PayNotifyTypeEnum.REFUND.getType(); + Long dataId = 1L; + // mock 方法(refund) + PayRefundDO refund = randomPojo(PayRefundDO.class); + when(refundService.getRefund(eq(1L))).thenReturn(refund); + // mock 方法(lock) + mockLock(null); // null 的原因,是咱没办法拿到 taskId 新增 + + // 调用 + notifyService.createPayNotifyTask(type, dataId); + // 断言,task + PayNotifyTaskDO dbTask = notifyTaskMapper.selectOne(null); + assertNotNull(dbTask.getNextNotifyTime()); + assertThat(dbTask) + .extracting("type", "dataId", "status", "notifyTimes", "maxNotifyTimes", + "appId", "merchantOrderId", "notifyUrl") + .containsExactly(type, dataId, PayNotifyStatusEnum.WAITING.getStatus(), 0, 9, + refund.getAppId(), refund.getMerchantOrderId(), refund.getNotifyUrl()); + // 断言,调用 + verify(payNotifyService).executeNotify0(eq(dbTask)); + } + } + + @Test + public void testExecuteNotify() throws InterruptedException { + // mock 数据(notify) + PayNotifyTaskDO dbTask01 = randomPojo(PayNotifyTaskDO.class, + o -> o.setStatus(PayNotifyStatusEnum.WAITING.getStatus()) + .setNextNotifyTime(addTime(Duration.ofMinutes(-1)))); + notifyTaskMapper.insert(dbTask01); + PayNotifyTaskDO dbTask02 = randomPojo(PayNotifyTaskDO.class, + o -> o.setStatus(PayNotifyStatusEnum.REQUEST_SUCCESS.getStatus()) + .setNextNotifyTime(addTime(Duration.ofMinutes(-1)))); + notifyTaskMapper.insert(dbTask02); + PayNotifyTaskDO dbTask03 = randomPojo(PayNotifyTaskDO.class, + o -> o.setStatus(PayNotifyStatusEnum.REQUEST_FAILURE.getStatus()) + .setNextNotifyTime(addTime(Duration.ofMinutes(-1)))); + notifyTaskMapper.insert(dbTask03); + PayNotifyTaskDO dbTask04 = randomPojo(PayNotifyTaskDO.class, // 不满足状态 + o -> o.setStatus(PayNotifyStatusEnum.FAILURE.getStatus()) + .setNextNotifyTime(addTime(Duration.ofMinutes(-1)))); + notifyTaskMapper.insert(dbTask04); + PayNotifyTaskDO dbTask05 = randomPojo(PayNotifyTaskDO.class, // 不满足状态 + o -> o.setStatus(PayNotifyStatusEnum.SUCCESS.getStatus()) + .setNextNotifyTime(addTime(Duration.ofMinutes(-1)))); + notifyTaskMapper.insert(dbTask05); + PayNotifyTaskDO dbTask06 = randomPojo(PayNotifyTaskDO.class, // 不满足时间 + o -> o.setStatus(PayNotifyStatusEnum.SUCCESS.getStatus()) + .setNextNotifyTime(addTime(Duration.ofMinutes(1)))); + notifyTaskMapper.insert(dbTask06); + // mock 方法(lock) + mockLock(dbTask01.getId()); + mockLock(dbTask02.getId()); + mockLock(dbTask03.getId()); + + // 调用 + int count = notifyService.executeNotify(); + // 断言,数量 + assertEquals(count, 3); + } + + @Test // 由于 HttpUtil 不好 mock,所以只测试异常的情况 + public void testExecuteNotify0_exception() { + // mock 数据(task) + PayNotifyTaskDO task = randomPojo(PayNotifyTaskDO.class, o -> o.setType(-1) + .setNotifyTimes(0).setMaxNotifyTimes(9)); + notifyTaskMapper.insert(task); + + // 调用 + notifyService.executeNotify0(task); + // 断言,task + PayNotifyTaskDO dbTask = notifyTaskMapper.selectById(task.getId()); + assertNotEquals(task.getNextNotifyTime(), dbTask.getNextNotifyTime()); + assertNotEquals(task.getLastExecuteTime(), dbTask.getNextNotifyTime()); + assertEquals(dbTask.getNotifyTimes(), 1); + assertEquals(dbTask.getStatus(), PayNotifyStatusEnum.REQUEST_FAILURE.getStatus()); + // 断言,log + PayNotifyLogDO dbLog = notifyLogMapper.selectOne(null); + assertEquals(dbLog.getTaskId(), task.getId()); + assertEquals(dbLog.getNotifyTimes(), 1); + assertTrue(dbLog.getResponse().contains("未知的通知任务类型:")); + assertEquals(dbLog.getStatus(), PayNotifyStatusEnum.REQUEST_FAILURE.getStatus()); + } + + @Test + public void testProcessNotifyResult_success() { + // mock 数据(task) + PayNotifyTaskDO task = randomPojo(PayNotifyTaskDO.class, + o -> o.setNotifyTimes(0).setMaxNotifyTimes(9)); + notifyTaskMapper.insert(task); + // 准备参数 + CommonResult invokeResult = CommonResult.success(randomString()); + + // 调用 + notifyService.processNotifyResult(task, invokeResult, null); + // 断言 + PayNotifyTaskDO dbTask = notifyTaskMapper.selectById(task.getId()); + assertEquals(task.getNextNotifyTime(), dbTask.getNextNotifyTime()); + assertNotEquals(task.getLastExecuteTime(), dbTask.getNextNotifyTime()); + assertEquals(dbTask.getNotifyTimes(), 1); + assertEquals(dbTask.getStatus(), PayNotifyStatusEnum.SUCCESS.getStatus()); + } + + @Test + public void testProcessNotifyResult_failure() { + // mock 数据(task) + PayNotifyTaskDO task = randomPojo(PayNotifyTaskDO.class, + o -> o.setNotifyTimes(8).setMaxNotifyTimes(9)); + notifyTaskMapper.insert(task); + // 准备参数 + CommonResult invokeResult = CommonResult.error(BAD_REQUEST); + + // 调用 + notifyService.processNotifyResult(task, invokeResult, null); + // 断言 + PayNotifyTaskDO dbTask = notifyTaskMapper.selectById(task.getId()); + assertEquals(task.getNextNotifyTime(), dbTask.getNextNotifyTime()); + assertNotEquals(task.getLastExecuteTime(), dbTask.getNextNotifyTime()); + assertEquals(dbTask.getNotifyTimes(), 9); + assertEquals(dbTask.getStatus(), PayNotifyStatusEnum.FAILURE.getStatus()); + } + + @Test + public void testProcessNotifyResult_requestFailure() { + // mock 数据(task) + PayNotifyTaskDO task = randomPojo(PayNotifyTaskDO.class, + o -> o.setNotifyTimes(0).setMaxNotifyTimes(9)); + notifyTaskMapper.insert(task); + // 准备参数 + CommonResult invokeResult = CommonResult.error(BAD_REQUEST); + + // 调用 + notifyService.processNotifyResult(task, invokeResult, null); + // 断言 + PayNotifyTaskDO dbTask = notifyTaskMapper.selectById(task.getId()); + assertNotEquals(task.getNextNotifyTime(), dbTask.getNextNotifyTime()); + assertNotEquals(task.getLastExecuteTime(), dbTask.getNextNotifyTime()); + assertEquals(dbTask.getNotifyTimes(), 1); + assertEquals(dbTask.getStatus(), PayNotifyStatusEnum.REQUEST_SUCCESS.getStatus()); + } + + @Test + public void testProcessNotifyResult_requestSuccess() { + // mock 数据(task) + PayNotifyTaskDO task = randomPojo(PayNotifyTaskDO.class, + o -> o.setNotifyTimes(0).setMaxNotifyTimes(9)); + notifyTaskMapper.insert(task); + // 准备参数 + CommonResult invokeResult = CommonResult.error(BAD_REQUEST); + RuntimeException invokeException = new RuntimeException(); + + // 调用 + notifyService.processNotifyResult(task, invokeResult, invokeException); + // 断言 + PayNotifyTaskDO dbTask = notifyTaskMapper.selectById(task.getId()); + assertNotEquals(task.getNextNotifyTime(), dbTask.getNextNotifyTime()); + assertNotEquals(task.getLastExecuteTime(), dbTask.getNextNotifyTime()); + assertEquals(dbTask.getNotifyTimes(), 1); + assertEquals(dbTask.getStatus(), PayNotifyStatusEnum.REQUEST_FAILURE.getStatus()); + } + + @Test + public void testGetNotifyTask() { + // mock 数据(task) + PayNotifyTaskDO task = randomPojo(PayNotifyTaskDO.class); + notifyTaskMapper.insert(task); + // 准备参数 + Long id = task.getId(); + + // 调用 + PayNotifyTaskDO dbTask = notifyService.getNotifyTask(id); + // 断言 + assertPojoEquals(dbTask, task); + } + + @Test + public void testGetNotifyTaskPage() { + // mock 数据 + PayNotifyTaskDO dbTask = randomPojo(PayNotifyTaskDO.class, o -> { // 等会查询到 + o.setAppId(1L); + o.setType(PayNotifyTypeEnum.REFUND.getType()); + o.setDataId(100L); + o.setStatus(PayNotifyStatusEnum.SUCCESS.getStatus()); + o.setMerchantOrderId("P110"); + o.setCreateTime(buildTime(2023, 2, 3)); + }); + notifyTaskMapper.insert(dbTask); + // 测试 appId 不匹配 + notifyTaskMapper.insert(cloneIgnoreId(dbTask, o -> o.setAppId(2L))); + // 测试 type 不匹配 + notifyTaskMapper.insert(cloneIgnoreId(dbTask, o -> o.setType(PayNotifyTypeEnum.ORDER.getType()))); + // 测试 dataId 不匹配 + notifyTaskMapper.insert(cloneIgnoreId(dbTask, o -> o.setDataId(200L))); + // 测试 status 不匹配 + notifyTaskMapper.insert(cloneIgnoreId(dbTask, o -> o.setStatus(PayNotifyStatusEnum.FAILURE.getStatus()))); + // 测试 merchantOrderId 不匹配 + notifyTaskMapper.insert(cloneIgnoreId(dbTask, o -> o.setMerchantOrderId(randomString()))); + // 测试 createTime 不匹配 + notifyTaskMapper.insert(cloneIgnoreId(dbTask, o -> o.setCreateTime(buildTime(2023, 1, 1)))); + // 准备参数 + PayNotifyTaskPageReqVO reqVO = new PayNotifyTaskPageReqVO(); + reqVO.setAppId(1L); + reqVO.setType(PayNotifyTypeEnum.REFUND.getType()); + reqVO.setDataId(100L); + reqVO.setStatus(PayNotifyStatusEnum.SUCCESS.getStatus()); + reqVO.setMerchantOrderId("P110"); + reqVO.setCreateTime(buildBetweenTime(2023, 2, 1, 2023, 2, 28)); + + // 调用 + PageResult pageResult = notifyService.getNotifyTaskPage(reqVO); + // 断言 + assertEquals(1, pageResult.getTotal()); + assertEquals(1, pageResult.getList().size()); + assertPojoEquals(dbTask, pageResult.getList().get(0)); + } + + @Test + public void testGetNotifyLogList() { + // mock 数据 + PayNotifyLogDO dbLog = randomPojo(PayNotifyLogDO.class); + notifyLogMapper.insert(dbLog); + PayNotifyLogDO dbLog02 = randomPojo(PayNotifyLogDO.class); + notifyLogMapper.insert(dbLog02); + // 准备参数 + Long taskId = dbLog.getTaskId(); + + // 调用 + List logList = notifyService.getNotifyLogList(taskId); + // 断言 + assertEquals(logList.size(), 1); + assertPojoEquals(dbLog, logList.get(0)); + } + + private void mockLock(Long id) { + RLock lock = mock(RLock.class); + if (id == null) { + when(redissonClient.getLock(anyString())) + .thenReturn(lock); + } else { + when(redissonClient.getLock(eq("pay_notify:lock:" + id))) + .thenReturn(lock); + } + } + +} diff --git a/yudao-module-pay/src/test/java/cn/iocoder/yudao/module/pay/service/order/PayOrderServiceTest.java b/yudao-module-pay/src/test/java/cn/iocoder/yudao/module/pay/service/order/PayOrderServiceTest.java new file mode 100755 index 0000000..d5e449a --- /dev/null +++ b/yudao-module-pay/src/test/java/cn/iocoder/yudao/module/pay/service/order/PayOrderServiceTest.java @@ -0,0 +1,1104 @@ +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.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; +import cn.iocoder.yudao.module.pay.controller.admin.order.vo.PayOrderPageReqVO; +import cn.iocoder.yudao.module.pay.controller.admin.order.vo.PayOrderSubmitReqVO; +import cn.iocoder.yudao.module.pay.controller.admin.order.vo.PayOrderSubmitRespVO; +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; +import cn.iocoder.yudao.module.pay.dal.dataobject.order.PayOrderExtensionDO; +import cn.iocoder.yudao.module.pay.dal.mysql.order.PayOrderExtensionMapper; +import cn.iocoder.yudao.module.pay.dal.mysql.order.PayOrderMapper; +import cn.iocoder.yudao.module.pay.dal.redis.no.PayNoRedisDAO; +import cn.iocoder.yudao.module.pay.enums.notify.PayNotifyTypeEnum; +import cn.iocoder.yudao.module.pay.enums.order.PayOrderStatusEnum; +import cn.iocoder.yudao.module.pay.framework.pay.config.PayProperties; +import cn.iocoder.yudao.module.pay.service.app.PayAppService; +import cn.iocoder.yudao.module.pay.service.channel.PayChannelService; +import cn.iocoder.yudao.module.pay.service.notify.PayNotifyService; +import jakarta.annotation.Resource; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.MockedStatic; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.context.annotation.Import; + +import java.time.Duration; +import java.time.LocalDateTime; +import java.util.List; + +import static cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.*; +import static cn.iocoder.yudao.framework.common.util.json.JsonUtils.toJsonString; +import static cn.iocoder.yudao.framework.common.util.object.ObjectUtils.cloneIgnoreId; +import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertPojoEquals; +import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertServiceException; +import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.*; +import static cn.iocoder.yudao.module.pay.enums.ErrorCodeConstants.*; +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.Mockito.*; + +/** + * {@link PayOrderServiceImpl} 的单元测试类 + * + * @author 芋艿 + */ +@Import({PayOrderServiceImpl.class, PayNoRedisDAO.class}) +public class PayOrderServiceTest extends BaseDbAndRedisUnitTest { + + @Resource + private PayOrderServiceImpl orderService; + + @Resource + private PayOrderMapper orderMapper; + @Resource + private PayOrderExtensionMapper orderExtensionMapper; + + @MockBean + private PayProperties properties; + @MockBean + private PayAppService appService; + @MockBean + private PayChannelService channelService; + @MockBean + private PayNotifyService notifyService; + + @BeforeEach + public void setUp() { + when(properties.getOrderNotifyUrl()).thenReturn("http://127.0.0.1"); + } + + @Test + public void testGetOrder_id() { + // mock 数据(PayOrderDO) + PayOrderDO order = randomPojo(PayOrderDO.class); + orderMapper.insert(order); + // 准备参数 + Long id = order.getId(); + + // 调用 + PayOrderDO dbOrder = orderService.getOrder(id); + // 断言 + assertPojoEquals(dbOrder, order); + } + + @Test + public void testGetOrder_appIdAndMerchantOrderId() { + // mock 数据(PayOrderDO) + PayOrderDO order = randomPojo(PayOrderDO.class); + orderMapper.insert(order); + // 准备参数 + Long appId = order.getAppId(); + String merchantOrderId = order.getMerchantOrderId(); + + // 调用 + PayOrderDO dbOrder = orderService.getOrder(appId, merchantOrderId); + // 断言 + assertPojoEquals(dbOrder, order); + } + + @Test + public void testGetOrderCountByAppId() { + // mock 数据(PayOrderDO) + PayOrderDO order01 = randomPojo(PayOrderDO.class); + orderMapper.insert(order01); + PayOrderDO order02 = randomPojo(PayOrderDO.class); + orderMapper.insert(order02); + // 准备参数 + Long appId = order01.getAppId(); + + // 调用 + Long count = orderService.getOrderCountByAppId(appId); + // 断言 + assertEquals(count, 1L); + } + + @Test + public void testGetOrderPage() { + // mock 数据 + PayOrderDO dbOrder = randomPojo(PayOrderDO.class, o -> { // 等会查询到 + o.setAppId(1L); + o.setChannelCode(PayChannelEnum.WX_PUB.getCode()); + o.setMerchantOrderId("110"); + o.setChannelOrderNo("220"); + o.setNo("330"); + o.setStatus(PayOrderStatusEnum.SUCCESS.getStatus()); + o.setCreateTime(buildTime(2018, 1, 15)); + }); + orderMapper.insert(dbOrder); + // 测试 appId 不匹配 + orderMapper.insert(cloneIgnoreId(dbOrder, o -> o.setAppId(2L))); + // 测试 channelCode 不匹配 + orderMapper.insert(cloneIgnoreId(dbOrder, o -> o.setChannelCode(PayChannelEnum.ALIPAY_APP.getCode()))); + // 测试 merchantOrderId 不匹配 + orderMapper.insert(cloneIgnoreId(dbOrder, o -> o.setMerchantOrderId(randomString()))); + // 测试 channelOrderNo 不匹配 + orderMapper.insert(cloneIgnoreId(dbOrder, o -> o.setChannelOrderNo(randomString()))); + // 测试 no 不匹配 + orderMapper.insert(cloneIgnoreId(dbOrder, o -> o.setNo(randomString()))); + // 测试 status 不匹配 + orderMapper.insert(cloneIgnoreId(dbOrder, o -> o.setStatus(PayOrderStatusEnum.CLOSED.getStatus()))); + // 测试 createTime 不匹配 + orderMapper.insert(cloneIgnoreId(dbOrder, o -> o.setCreateTime(buildTime(2019, 1, 1)))); + // 准备参数 + PayOrderPageReqVO reqVO = new PayOrderPageReqVO(); + reqVO.setAppId(1L); + reqVO.setChannelCode(PayChannelEnum.WX_PUB.getCode()); + reqVO.setMerchantOrderId("11"); + reqVO.setChannelOrderNo("22"); + reqVO.setNo("33"); + reqVO.setStatus(PayOrderStatusEnum.SUCCESS.getStatus()); + reqVO.setCreateTime(buildBetweenTime(2018, 1, 10, 2018, 1, 30)); + + // 调用 + PageResult pageResult = orderService.getOrderPage(reqVO); + // 断言 + assertEquals(1, pageResult.getTotal()); + assertEquals(1, pageResult.getList().size()); + assertPojoEquals(dbOrder, pageResult.getList().get(0)); + } + + @Test + public void testGetOrderList() { + // mock 数据 + PayOrderDO dbOrder = randomPojo(PayOrderDO.class, o -> { // 等会查询到 + o.setAppId(1L); + o.setChannelCode(PayChannelEnum.WX_PUB.getCode()); + o.setMerchantOrderId("110"); + o.setChannelOrderNo("220"); + o.setNo("330"); + o.setStatus(PayOrderStatusEnum.SUCCESS.getStatus()); + o.setCreateTime(buildTime(2018, 1, 15)); + }); + orderMapper.insert(dbOrder); + // 测试 appId 不匹配 + orderMapper.insert(cloneIgnoreId(dbOrder, o -> o.setAppId(2L))); + // 测试 channelCode 不匹配 + orderMapper.insert(cloneIgnoreId(dbOrder, o -> o.setChannelCode(PayChannelEnum.ALIPAY_APP.getCode()))); + // 测试 merchantOrderId 不匹配 + orderMapper.insert(cloneIgnoreId(dbOrder, o -> o.setMerchantOrderId(randomString()))); + // 测试 channelOrderNo 不匹配 + orderMapper.insert(cloneIgnoreId(dbOrder, o -> o.setChannelOrderNo(randomString()))); + // 测试 no 不匹配 + orderMapper.insert(cloneIgnoreId(dbOrder, o -> o.setNo(randomString()))); + // 测试 status 不匹配 + orderMapper.insert(cloneIgnoreId(dbOrder, o -> o.setStatus(PayOrderStatusEnum.CLOSED.getStatus()))); + // 测试 createTime 不匹配 + orderMapper.insert(cloneIgnoreId(dbOrder, o -> o.setCreateTime(buildTime(2019, 1, 1)))); + // 准备参数 + PayOrderExportReqVO reqVO = new PayOrderExportReqVO(); + reqVO.setAppId(1L); + reqVO.setChannelCode(PayChannelEnum.WX_PUB.getCode()); + reqVO.setMerchantOrderId("11"); + reqVO.setChannelOrderNo("22"); + reqVO.setNo("33"); + reqVO.setStatus(PayOrderStatusEnum.SUCCESS.getStatus()); + reqVO.setCreateTime(buildBetweenTime(2018, 1, 10, 2018, 1, 30)); + + // 调用 + List list = orderService.getOrderList(reqVO); + // 断言 + assertEquals(1, list.size()); + assertPojoEquals(dbOrder, list.get(0)); + } + + @Test + public void testCreateOrder_success() { + // mock 参数 + PayOrderCreateReqDTO reqDTO = randomPojo(PayOrderCreateReqDTO.class, + o -> o.setAppKey("demo").setMerchantOrderId("10") + .setSubject(randomString()).setBody(randomString())); + // mock 方法 + PayAppDO app = randomPojo(PayAppDO.class, o -> o.setId(1L).setOrderNotifyUrl("http://127.0.0.1")); + when(appService.validPayApp(eq(reqDTO.getAppKey()))).thenReturn(app); + + // 调用 + Long orderId = orderService.createOrder(reqDTO); + // 断言 + PayOrderDO order = orderMapper.selectById(orderId); + assertPojoEquals(order, reqDTO); + assertEquals(order.getAppId(), 1L); + assertEquals(order.getNotifyUrl(), "http://127.0.0.1"); + assertEquals(order.getStatus(), PayOrderStatusEnum.WAITING.getStatus()); + assertEquals(order.getRefundPrice(), 0); + } + + @Test + public void testCreateOrder_exists() { + // mock 参数 + PayOrderCreateReqDTO reqDTO = randomPojo(PayOrderCreateReqDTO.class, + o -> o.setAppKey("demo").setMerchantOrderId("10")); + // mock 数据 + PayOrderDO dbOrder = randomPojo(PayOrderDO.class, o -> o.setAppId(1L).setMerchantOrderId("10")); + orderMapper.insert(dbOrder); + // mock 方法 + PayAppDO app = randomPojo(PayAppDO.class, o -> o.setId(1L).setOrderNotifyUrl("http://127.0.0.1")); + when(appService.validPayApp(eq(reqDTO.getAppKey()))).thenReturn(app); + + // 调用 + Long orderId = orderService.createOrder(reqDTO); + // 断言 + PayOrderDO order = orderMapper.selectById(orderId); + assertPojoEquals(dbOrder, order); + } + + @Test + public void testSubmitOrder_notFound() { + // 准备参数 + PayOrderSubmitReqVO reqVO = randomPojo(PayOrderSubmitReqVO.class); + String userIp = randomString(); + + // 调用, 并断言异常 + assertServiceException(() -> orderService.submitOrder(reqVO, userIp), PAY_ORDER_NOT_FOUND); + } + + @Test + public void testSubmitOrder_notWaiting() { + // mock 数据(order) + PayOrderDO order = randomPojo(PayOrderDO.class, o -> o.setStatus(PayOrderStatusEnum.REFUND.getStatus())); + orderMapper.insert(order); + // 准备参数 + PayOrderSubmitReqVO reqVO = randomPojo(PayOrderSubmitReqVO.class, o -> o.setId(order.getId())); + String userIp = randomString(); + + // 调用, 并断言异常 + assertServiceException(() -> orderService.submitOrder(reqVO, userIp), PAY_ORDER_STATUS_IS_NOT_WAITING); + } + + @Test + public void testSubmitOrder_isSuccess() { + // mock 数据(order) + PayOrderDO order = randomPojo(PayOrderDO.class, o -> o.setStatus(PayOrderStatusEnum.SUCCESS.getStatus())); + orderMapper.insert(order); + // 准备参数 + PayOrderSubmitReqVO reqVO = randomPojo(PayOrderSubmitReqVO.class, o -> o.setId(order.getId())); + String userIp = randomString(); + + // 调用, 并断言异常 + assertServiceException(() -> orderService.submitOrder(reqVO, userIp), PAY_ORDER_STATUS_IS_SUCCESS); + } + + @Test + public void testSubmitOrder_expired() { + // mock 数据(order) + PayOrderDO order = randomPojo(PayOrderDO.class, o -> o.setStatus(PayOrderStatusEnum.WAITING.getStatus()) + .setExpireTime(addTime(Duration.ofDays(-1)))); + orderMapper.insert(order); + // 准备参数 + PayOrderSubmitReqVO reqVO = randomPojo(PayOrderSubmitReqVO.class, o -> o.setId(order.getId())); + String userIp = randomString(); + + // 调用, 并断言异常 + assertServiceException(() -> orderService.submitOrder(reqVO, userIp), PAY_ORDER_IS_EXPIRED); + } + + @Test + public void testSubmitOrder_channelNotFound() { + // mock 数据(order) + PayOrderDO order = randomPojo(PayOrderDO.class, o -> o.setStatus(PayOrderStatusEnum.WAITING.getStatus()) + .setAppId(1L).setExpireTime(addTime(Duration.ofDays(1)))); + orderMapper.insert(order); + // 准备参数 + PayOrderSubmitReqVO reqVO = randomPojo(PayOrderSubmitReqVO.class, o -> o.setId(order.getId()) + .setChannelCode(PayChannelEnum.ALIPAY_APP.getCode())); + String userIp = randomString(); + // mock 方法(app) + PayAppDO app = randomPojo(PayAppDO.class, o -> o.setId(1L)); + when(appService.validPayApp(eq(1L))).thenReturn(app); + // mock 方法(channel) + PayChannelDO channel = randomPojo(PayChannelDO.class, o -> o.setCode(PayChannelEnum.ALIPAY_APP.getCode())); + when(channelService.validPayChannel(eq(1L), eq(PayChannelEnum.ALIPAY_APP.getCode()))) + .thenReturn(channel); + + // 调用, 并断言异常 + assertServiceException(() -> orderService.submitOrder(reqVO, userIp), CHANNEL_NOT_FOUND); + } + + @Test // 调用 unifiedOrder 接口,返回存在渠道错误 + public void testSubmitOrder_channelError() { + PayOrderServiceImpl payOrderServiceImpl = mock(PayOrderServiceImpl.class); + try (MockedStatic springUtilMockedStatic = mockStatic(SpringUtil.class)) { + springUtilMockedStatic.when(() -> SpringUtil.getBean(eq(PayOrderServiceImpl.class))) + .thenReturn(payOrderServiceImpl); + + // mock 数据(order) + PayOrderDO order = randomPojo(PayOrderDO.class, o -> o.setStatus(PayOrderStatusEnum.WAITING.getStatus()) + .setAppId(1L).setExpireTime(addTime(Duration.ofDays(1)))); + orderMapper.insert(order); + // 准备参数 + PayOrderSubmitReqVO reqVO = randomPojo(PayOrderSubmitReqVO.class, o -> o.setId(order.getId()) + .setChannelCode(PayChannelEnum.ALIPAY_APP.getCode())); + String userIp = randomString(); + // mock 方法(app) + PayAppDO app = randomPojo(PayAppDO.class, o -> o.setId(1L)); + when(appService.validPayApp(eq(1L))).thenReturn(app); + // mock 方法(channel) + PayChannelDO channel = randomPojo(PayChannelDO.class, o -> o.setId(10L) + .setCode(PayChannelEnum.ALIPAY_APP.getCode())); + when(channelService.validPayChannel(eq(1L), eq(PayChannelEnum.ALIPAY_APP.getCode()))) + .thenReturn(channel); + // mock 方法(client) + PayClient client = mock(PayClient.class); + when(channelService.getPayClient(eq(10L))).thenReturn(client); + // mock 方法() + PayOrderRespDTO unifiedOrderResp = randomPojo(PayOrderRespDTO.class, o -> + o.setChannelErrorCode("001").setChannelErrorMsg("模拟异常")); + when(client.unifiedOrder(argThat(payOrderUnifiedReqDTO -> { + assertNotNull(payOrderUnifiedReqDTO.getOutTradeNo()); + assertThat(payOrderUnifiedReqDTO) + .extracting("subject", "body", "notifyUrl", "returnUrl", "price", "expireTime") + .containsExactly(order.getSubject(), order.getBody(), "http://127.0.0.1/10", + reqVO.getReturnUrl(), order.getPrice(), order.getExpireTime()); + return true; + }))).thenReturn(unifiedOrderResp); + + // 调用,并断言异常 + assertServiceException(() -> orderService.submitOrder(reqVO, userIp), + PAY_ORDER_SUBMIT_CHANNEL_ERROR, "001", "模拟异常"); + // 断言,数据记录(PayOrderExtensionDO) + PayOrderExtensionDO orderExtension = orderExtensionMapper.selectOne(null); + assertNotNull(orderExtension); + assertThat(orderExtension).extracting("no", "orderId").isNotNull(); + assertThat(orderExtension) + .extracting("channelId", "channelCode","userIp" ,"status", "channelExtras", + "channelErrorCode", "channelErrorMsg", "channelNotifyData") + .containsExactly(10L, PayChannelEnum.ALIPAY_APP.getCode(), userIp, + PayOrderStatusEnum.WAITING.getStatus(), reqVO.getChannelExtras(), + null, null, null); + } + } + + @Test + public void testSubmitOrder_success() { + PayOrderServiceImpl payOrderServiceImpl = mock(PayOrderServiceImpl.class); + try (MockedStatic springUtilMockedStatic = mockStatic(SpringUtil.class)) { + springUtilMockedStatic.when(() -> SpringUtil.getBean(eq(PayOrderServiceImpl.class))) + .thenReturn(payOrderServiceImpl); + + // mock 数据(order) + PayOrderDO order = randomPojo(PayOrderDO.class, o -> o.setStatus(PayOrderStatusEnum.WAITING.getStatus()) + .setAppId(1L).setExpireTime(addTime(Duration.ofDays(1)))); + orderMapper.insert(order); + // 准备参数 + PayOrderSubmitReqVO reqVO = randomPojo(PayOrderSubmitReqVO.class, o -> o.setId(order.getId()) + .setChannelCode(PayChannelEnum.ALIPAY_APP.getCode())); + String userIp = randomString(); + // mock 方法(app) + PayAppDO app = randomPojo(PayAppDO.class, o -> o.setId(1L)); + when(appService.validPayApp(eq(1L))).thenReturn(app); + // mock 方法(channel) + PayChannelDO channel = randomPojo(PayChannelDO.class, o -> o.setId(10L) + .setCode(PayChannelEnum.ALIPAY_APP.getCode())); + when(channelService.validPayChannel(eq(1L), eq(PayChannelEnum.ALIPAY_APP.getCode()))) + .thenReturn(channel); + // mock 方法(client) + PayClient client = mock(PayClient.class); + when(channelService.getPayClient(eq(10L))).thenReturn(client); + // mock 方法(支付渠道的调用) + PayOrderRespDTO unifiedOrderResp = randomPojo(PayOrderRespDTO.class, o -> o.setChannelErrorCode(null).setChannelErrorMsg(null) + .setDisplayMode(PayOrderDisplayModeEnum.URL.getMode()).setDisplayContent("tudou")); + when(client.unifiedOrder(argThat(payOrderUnifiedReqDTO -> { + assertNotNull(payOrderUnifiedReqDTO.getOutTradeNo()); + assertThat(payOrderUnifiedReqDTO) + .extracting("subject", "body", "notifyUrl", "returnUrl", "price", "expireTime") + .containsExactly(order.getSubject(), order.getBody(), "http://127.0.0.1/10", + reqVO.getReturnUrl(), order.getPrice(), order.getExpireTime()); + return true; + }))).thenReturn(unifiedOrderResp); + + // 调用 + PayOrderSubmitRespVO result = orderService.submitOrder(reqVO, userIp); + // 断言,数据记录(PayOrderExtensionDO) + PayOrderExtensionDO orderExtension = orderExtensionMapper.selectOne(null); + assertNotNull(orderExtension); + assertThat(orderExtension).extracting("no", "orderId").isNotNull(); + assertThat(orderExtension) + .extracting("channelId", "channelCode","userIp" ,"status", "channelExtras", + "channelErrorCode", "channelErrorMsg", "channelNotifyData") + .containsExactly(10L, PayChannelEnum.ALIPAY_APP.getCode(), userIp, + PayOrderStatusEnum.WAITING.getStatus(), reqVO.getChannelExtras(), + null, null, null); + // 断言,返回(PayOrderSubmitRespVO) + assertThat(result) + .extracting("status", "displayMode", "displayContent") + .containsExactly(PayOrderStatusEnum.WAITING.getStatus(), PayOrderDisplayModeEnum.URL.getMode(), "tudou"); + // 断言,调用 + verify(payOrderServiceImpl).notifyOrder(same(channel), same(unifiedOrderResp)); + } + } + + @Test + public void testValidateOrderActuallyPaid_dbPaid() { + // 准备参数 + Long id = randomLongId(); + // mock 方法(OrderExtension 已支付) + PayOrderExtensionDO orderExtension = randomPojo(PayOrderExtensionDO.class, + o -> o.setOrderId(id).setStatus(PayOrderStatusEnum.SUCCESS.getStatus())); + orderExtensionMapper.insert(orderExtension); + + // 调用,并断言异常 + assertServiceException(() -> orderService.validateOrderActuallyPaid(id), + PAY_ORDER_EXTENSION_IS_PAID); + } + + @Test + public void testValidateOrderActuallyPaid_remotePaid() { + // 准备参数 + Long id = randomLongId(); + // mock 方法(OrderExtension 已支付) + PayOrderExtensionDO orderExtension = randomPojo(PayOrderExtensionDO.class, + o -> o.setOrderId(id).setStatus(PayOrderStatusEnum.WAITING.getStatus())); + orderExtensionMapper.insert(orderExtension); + // mock 方法(PayClient 已支付) + PayClient client = mock(PayClient.class); + when(channelService.getPayClient(eq(orderExtension.getChannelId()))).thenReturn(client); + when(client.getOrder(eq(orderExtension.getNo()))).thenReturn(randomPojo(PayOrderRespDTO.class, + o -> o.setStatus(PayOrderStatusEnum.SUCCESS.getStatus()))); + + // 调用,并断言异常 + assertServiceException(() -> orderService.validateOrderActuallyPaid(id), + PAY_ORDER_EXTENSION_IS_PAID); + } + + @Test + public void testValidateOrderActuallyPaid_success() { + // 准备参数 + Long id = randomLongId(); + // mock 方法(OrderExtension 已支付) + PayOrderExtensionDO orderExtension = randomPojo(PayOrderExtensionDO.class, + o -> o.setOrderId(id).setStatus(PayOrderStatusEnum.WAITING.getStatus())); + orderExtensionMapper.insert(orderExtension); + // mock 方法(PayClient 已支付) + PayClient client = mock(PayClient.class); + when(channelService.getPayClient(eq(orderExtension.getChannelId()))).thenReturn(client); + when(client.getOrder(eq(orderExtension.getNo()))).thenReturn(randomPojo(PayOrderRespDTO.class, + o -> o.setStatus(PayOrderStatusEnum.WAITING.getStatus()))); + + // 调用,并断言异常 + orderService.validateOrderActuallyPaid(id); + } + + @Test + public void testNotifyOrder_channelId() { + PayOrderServiceImpl payOrderServiceImpl = mock(PayOrderServiceImpl.class); + try (MockedStatic springUtilMockedStatic = mockStatic(SpringUtil.class)) { + springUtilMockedStatic.when(() -> SpringUtil.getBean(eq(PayOrderServiceImpl.class))) + .thenReturn(payOrderServiceImpl); + // 准备参数 + Long channelId = 10L; + PayOrderRespDTO notify = randomPojo(PayOrderRespDTO.class); + // mock 方法(channel) + PayChannelDO channel = randomPojo(PayChannelDO.class, o -> o.setId(10L)); + when(channelService.validPayChannel(eq(10L))).thenReturn(channel); + + // 调用 + orderService.notifyOrder(channelId, notify); + // 断言 + verify(payOrderServiceImpl).notifyOrder(same(channel), same(notify)); + } + } + + @Test + public void testNotifyOrderSuccess_orderExtension_notFound() { + // 准备参数 + PayChannelDO channel = randomPojo(PayChannelDO.class, o -> o.setId(10L)); + PayOrderRespDTO notify = randomPojo(PayOrderRespDTO.class, + o -> o.setStatus(PayOrderStatusEnum.SUCCESS.getStatus())); + + // 调用,并断言异常 + assertServiceException(() -> orderService.notifyOrder(channel, notify), + PAY_ORDER_EXTENSION_NOT_FOUND); + } + + @Test + public void testNotifyOrderSuccess_orderExtension_closed() { + // mock 数据(PayOrderExtensionDO) + PayOrderExtensionDO orderExtension = randomPojo(PayOrderExtensionDO.class, + o -> o.setStatus(PayOrderStatusEnum.CLOSED.getStatus()) + .setNo("P110")); + orderExtensionMapper.insert(orderExtension); + // 准备参数 + PayChannelDO channel = randomPojo(PayChannelDO.class, o -> o.setId(10L)); + PayOrderRespDTO notify = randomPojo(PayOrderRespDTO.class, + o -> o.setStatus(PayOrderStatusEnum.SUCCESS.getStatus()) + .setOutTradeNo("P110")); + + // 调用,并断言异常 + assertServiceException(() -> orderService.notifyOrder(channel, notify), + PAY_ORDER_EXTENSION_STATUS_IS_NOT_WAITING); + } + + @Test + public void testNotifyOrderSuccess_order_notFound() { + // mock 数据(PayOrderExtensionDO) + PayOrderExtensionDO orderExtension = randomPojo(PayOrderExtensionDO.class, + o -> o.setStatus(PayOrderStatusEnum.SUCCESS.getStatus()) + .setNo("P110")); + orderExtensionMapper.insert(orderExtension); + // 准备参数 + PayChannelDO channel = randomPojo(PayChannelDO.class, o -> o.setId(10L)); + PayOrderRespDTO notify = randomPojo(PayOrderRespDTO.class, + o -> o.setStatus(PayOrderStatusEnum.SUCCESS.getStatus()) + .setOutTradeNo("P110")); + + // 调用,并断言异常 + assertServiceException(() -> orderService.notifyOrder(channel, notify), + PAY_ORDER_NOT_FOUND); + // 断言 PayOrderExtensionDO :数据更新被回滚 + assertPojoEquals(orderExtension, orderExtensionMapper.selectOne(null)); + } + + @Test + public void testNotifyOrderSuccess_order_closed() { + testNotifyOrderSuccess_order_closedOrRefund(PayOrderStatusEnum.CLOSED.getStatus()); + } + + @Test + public void testNotifyOrderSuccess_order_refund() { + testNotifyOrderSuccess_order_closedOrRefund(PayOrderStatusEnum.REFUND.getStatus()); + } + + private void testNotifyOrderSuccess_order_closedOrRefund(Integer status) { + // mock 数据(PayOrderDO) + PayOrderDO order = randomPojo(PayOrderDO.class, o -> o.setStatus(status)); + orderMapper.insert(order); + // mock 数据(PayOrderExtensionDO) + PayOrderExtensionDO orderExtension = randomPojo(PayOrderExtensionDO.class, + o -> o.setStatus(PayOrderStatusEnum.SUCCESS.getStatus()) + .setNo("P110") + .setOrderId(order.getId())); + orderExtensionMapper.insert(orderExtension); + // 准备参数 + PayChannelDO channel = randomPojo(PayChannelDO.class, o -> o.setId(10L)); + PayOrderRespDTO notify = randomPojo(PayOrderRespDTO.class, + o -> o.setStatus(PayOrderStatusEnum.SUCCESS.getStatus()) + .setOutTradeNo("P110")); + + // 调用,并断言异常 + assertServiceException(() -> orderService.notifyOrder(channel, notify), + PAY_ORDER_STATUS_IS_NOT_WAITING); + // 断言 PayOrderExtensionDO :数据未更新,因为它是 SUCCESS + assertPojoEquals(orderExtension, orderExtensionMapper.selectOne(null)); + } + + @Test + public void testNotifyOrderSuccess_order_paid() { + // mock 数据(PayOrderDO) + PayOrderDO order = randomPojo(PayOrderDO.class, + o -> o.setStatus(PayOrderStatusEnum.SUCCESS.getStatus())); + orderMapper.insert(order); + // mock 数据(PayOrderExtensionDO) + PayOrderExtensionDO orderExtension = randomPojo(PayOrderExtensionDO.class, + o -> o.setStatus(PayOrderStatusEnum.SUCCESS.getStatus()) + .setNo("P110") + .setOrderId(order.getId())); + orderExtensionMapper.insert(orderExtension); + // 重要:需要将 order 的 extensionId 更新下 + order.setExtensionId(orderExtension.getId()); + orderMapper.updateById(order); + // 准备参数 + PayChannelDO channel = randomPojo(PayChannelDO.class, o -> o.setId(10L)); + PayOrderRespDTO notify = randomPojo(PayOrderRespDTO.class, + o -> o.setStatus(PayOrderStatusEnum.SUCCESS.getStatus()) + .setOutTradeNo("P110")); + + // 调用,并断言异常 + orderService.notifyOrder(channel, notify); + // 断言 PayOrderExtensionDO :数据未更新,因为它是 SUCCESS + assertPojoEquals(orderExtension, orderExtensionMapper.selectOne(null)); + // 断言 PayOrderDO :数据未更新,因为它是 SUCCESS + assertPojoEquals(order, orderMapper.selectOne(null)); + // 断言,调用 + verify(notifyService, never()).createPayNotifyTask(anyInt(), anyLong()); + } + + @Test + public void testNotifyOrderSuccess_order_waiting() { + // mock 数据(PayOrderDO) + PayOrderDO order = randomPojo(PayOrderDO.class, + o -> o.setStatus(PayOrderStatusEnum.WAITING.getStatus()) + .setPrice(10)); + orderMapper.insert(order); + // mock 数据(PayOrderExtensionDO) + PayOrderExtensionDO orderExtension = randomPojo(PayOrderExtensionDO.class, + o -> o.setStatus(PayOrderStatusEnum.WAITING.getStatus()) + .setNo("P110") + .setOrderId(order.getId())); + orderExtensionMapper.insert(orderExtension); + // 准备参数 + PayChannelDO channel = randomPojo(PayChannelDO.class, o -> o.setId(10L) + .setFeeRate(10D)); + PayOrderRespDTO notify = randomPojo(PayOrderRespDTO.class, + o -> o.setStatus(PayOrderStatusEnum.SUCCESS.getStatus()) + .setOutTradeNo("P110")); + + // 调用,并断言异常 + orderService.notifyOrder(channel, notify); + // 断言 PayOrderExtensionDO :数据未更新,因为它是 SUCCESS + orderExtension.setStatus(PayOrderStatusEnum.SUCCESS.getStatus()) + .setChannelNotifyData(toJsonString(notify)); + assertPojoEquals(orderExtension, orderExtensionMapper.selectOne(null), + "updateTime", "updater"); + // 断言 PayOrderDO :数据未更新,因为它是 SUCCESS + order.setStatus(PayOrderStatusEnum.SUCCESS.getStatus()) + .setChannelId(10L).setChannelCode(channel.getCode()) + .setSuccessTime(notify.getSuccessTime()).setExtensionId(orderExtension.getId()).setNo(orderExtension.getNo()) + .setChannelOrderNo(notify.getChannelOrderNo()).setChannelUserId(notify.getChannelUserId()) + .setChannelFeeRate(10D).setChannelFeePrice(1); + assertPojoEquals(order, orderMapper.selectOne(null), + "updateTime", "updater"); + // 断言,调用 + verify(notifyService).createPayNotifyTask(eq(PayNotifyTypeEnum.ORDER.getType()), + eq(orderExtension.getOrderId())); + } + + @Test + public void testNotifyOrderClosed_orderExtension_notFound() { + // 准备参数 + PayChannelDO channel = randomPojo(PayChannelDO.class, o -> o.setId(10L)); + PayOrderRespDTO notify = randomPojo(PayOrderRespDTO.class, + o -> o.setStatus(PayOrderStatusEnum.CLOSED.getStatus())); + + // 调用,并断言异常 + assertServiceException(() -> orderService.notifyOrder(channel, notify), + PAY_ORDER_EXTENSION_NOT_FOUND); + } + + @Test + public void testNotifyOrderClosed_orderExtension_closed() { + // mock 数据(PayOrderExtensionDO) + PayOrderExtensionDO orderExtension = randomPojo(PayOrderExtensionDO.class, + o -> o.setStatus(PayOrderStatusEnum.CLOSED.getStatus()) + .setNo("P110")); + orderExtensionMapper.insert(orderExtension); + // 准备参数 + PayChannelDO channel = randomPojo(PayChannelDO.class, o -> o.setId(10L)); + PayOrderRespDTO notify = randomPojo(PayOrderRespDTO.class, + o -> o.setStatus(PayOrderStatusEnum.CLOSED.getStatus()) + .setOutTradeNo("P110")); + + // 调用,并断言 + orderService.notifyOrder(channel, notify); + // 断言 PayOrderExtensionDO :数据未更新,因为它是 CLOSED + assertPojoEquals(orderExtension, orderExtensionMapper.selectOne(null)); + } + + @Test + public void testNotifyOrderClosed_orderExtension_paid() { + // mock 数据(PayOrderExtensionDO) + PayOrderExtensionDO orderExtension = randomPojo(PayOrderExtensionDO.class, + o -> o.setStatus(PayOrderStatusEnum.SUCCESS.getStatus()) + .setNo("P110")); + orderExtensionMapper.insert(orderExtension); + // 准备参数 + PayChannelDO channel = randomPojo(PayChannelDO.class, o -> o.setId(10L)); + PayOrderRespDTO notify = randomPojo(PayOrderRespDTO.class, + o -> o.setStatus(PayOrderStatusEnum.CLOSED.getStatus()) + .setOutTradeNo("P110")); + + // 调用,并断言 + orderService.notifyOrder(channel, notify); + // 断言 PayOrderExtensionDO :数据未更新,因为它是 SUCCESS + assertPojoEquals(orderExtension, orderExtensionMapper.selectOne(null)); + } + + @Test + public void testNotifyOrderClosed_orderExtension_refund() { + // mock 数据(PayOrderExtensionDO) + PayOrderExtensionDO orderExtension = randomPojo(PayOrderExtensionDO.class, + o -> o.setStatus(PayOrderStatusEnum.REFUND.getStatus()) + .setNo("P110")); + orderExtensionMapper.insert(orderExtension); + // 准备参数 + PayChannelDO channel = randomPojo(PayChannelDO.class, o -> o.setId(10L)); + PayOrderRespDTO notify = randomPojo(PayOrderRespDTO.class, + o -> o.setStatus(PayOrderStatusEnum.CLOSED.getStatus()) + .setOutTradeNo("P110")); + + // 调用,并断言异常 + assertServiceException(() -> orderService.notifyOrder(channel, notify), + PAY_ORDER_EXTENSION_STATUS_IS_NOT_WAITING); + } + + @Test + public void testNotifyOrderClosed_orderExtension_waiting() { + // mock 数据(PayOrderExtensionDO) + PayOrderExtensionDO orderExtension = randomPojo(PayOrderExtensionDO.class, + o -> o.setStatus(PayOrderStatusEnum.WAITING.getStatus()) + .setNo("P110")); + orderExtensionMapper.insert(orderExtension); + // 准备参数 + PayChannelDO channel = randomPojo(PayChannelDO.class, o -> o.setId(10L)); + PayOrderRespDTO notify = randomPojo(PayOrderRespDTO.class, + o -> o.setStatus(PayOrderStatusEnum.CLOSED.getStatus()) + .setOutTradeNo("P110")); + + // 调用 + orderService.notifyOrder(channel, notify); + // 断言 PayOrderExtensionDO + orderExtension.setStatus(PayOrderStatusEnum.CLOSED.getStatus()).setChannelNotifyData(toJsonString(notify)) + .setChannelErrorCode(notify.getChannelErrorCode()).setChannelErrorMsg(notify.getChannelErrorMsg()); + assertPojoEquals(orderExtension, orderExtensionMapper.selectOne(null), + "updateTime", "updater"); + } + + @Test + public void testUpdateOrderRefundPrice_notFound() { + // 准备参数 + Long id = randomLongId(); + Integer incrRefundPrice = randomInteger(); + + // 调用,并断言异常 + assertServiceException(() -> orderService.updateOrderRefundPrice(id, incrRefundPrice), + PAY_ORDER_NOT_FOUND); + } + + @Test + public void testUpdateOrderRefundPrice_waiting() { + testUpdateOrderRefundPrice_waitingOrClosed(PayOrderStatusEnum.WAITING.getStatus()); + } + + @Test + public void testUpdateOrderRefundPrice_closed() { + testUpdateOrderRefundPrice_waitingOrClosed(PayOrderStatusEnum.CLOSED.getStatus()); + } + + private void testUpdateOrderRefundPrice_waitingOrClosed(Integer status) { + // mock 数据(PayOrderDO) + PayOrderDO order = randomPojo(PayOrderDO.class, + o -> o.setStatus(status)); + orderMapper.insert(order); + // 准备参数 + Long id = order.getId(); + Integer incrRefundPrice = randomInteger(); + + // 调用,并断言异常 + assertServiceException(() -> orderService.updateOrderRefundPrice(id, incrRefundPrice), + PAY_ORDER_REFUND_FAIL_STATUS_ERROR); + } + + @Test + public void testUpdateOrderRefundPrice_priceExceed() { + // mock 数据(PayOrderDO) + PayOrderDO order = randomPojo(PayOrderDO.class, + o -> o.setStatus(PayOrderStatusEnum.SUCCESS.getStatus()) + .setRefundPrice(1).setPrice(10)); + orderMapper.insert(order); + // 准备参数 + Long id = order.getId(); + Integer incrRefundPrice = 10; + + // 调用,并断言异常 + assertServiceException(() -> orderService.updateOrderRefundPrice(id, incrRefundPrice), + REFUND_PRICE_EXCEED); + } + + @Test + public void testUpdateOrderRefundPrice_refund() { + testUpdateOrderRefundPrice_refundOrSuccess(PayOrderStatusEnum.REFUND.getStatus()); + } + + @Test + public void testUpdateOrderRefundPrice_success() { + testUpdateOrderRefundPrice_refundOrSuccess(PayOrderStatusEnum.SUCCESS.getStatus()); + } + + private void testUpdateOrderRefundPrice_refundOrSuccess(Integer status) { + // mock 数据(PayOrderDO) + PayOrderDO order = randomPojo(PayOrderDO.class, + o -> o.setStatus(status).setRefundPrice(1).setPrice(10)); + orderMapper.insert(order); + // 准备参数 + Long id = order.getId(); + Integer incrRefundPrice = 8; + + // 调用 + orderService.updateOrderRefundPrice(id, incrRefundPrice); + // 断言 + order.setRefundPrice(9).setStatus(PayOrderStatusEnum.REFUND.getStatus()); + assertPojoEquals(order, orderMapper.selectOne(null), + "updateTime", "updater"); + } + + @Test + public void testGetOrderExtension() { + // mock 数据(PayOrderExtensionDO) + PayOrderExtensionDO orderExtension = randomPojo(PayOrderExtensionDO.class); + orderExtensionMapper.insert(orderExtension); + // 准备参数 + Long id = orderExtension.getId(); + + // 调用 + PayOrderExtensionDO dbOrderExtension = orderService.getOrderExtension(id); + // 断言 + assertPojoEquals(dbOrderExtension, orderExtension); + } + + @Test + public void testSyncOrder_payClientNotFound() { + // 准备参数 + LocalDateTime minCreateTime = LocalDateTime.now().minus(Duration.ofMinutes(10)); + // mock 数据(PayOrderExtensionDO) + PayOrderExtensionDO orderExtension = randomPojo(PayOrderExtensionDO.class, + o -> o.setStatus(PayOrderStatusEnum.WAITING.getStatus()) + .setCreateTime(LocalDateTime.now())); + orderExtensionMapper.insert(orderExtension); + + // 调用 + int count = orderService.syncOrder(minCreateTime); + // 断言 + assertEquals(count, 0); + } + + @Test + public void testSyncOrder_exception() { + // 准备参数 + LocalDateTime minCreateTime = LocalDateTime.now().minus(Duration.ofMinutes(10)); + // mock 数据(PayOrderExtensionDO) + PayOrderExtensionDO orderExtension = randomPojo(PayOrderExtensionDO.class, + o -> o.setStatus(PayOrderStatusEnum.WAITING.getStatus()) + .setChannelId(10L) + .setCreateTime(LocalDateTime.now())); + orderExtensionMapper.insert(orderExtension); + // mock 方法(PayClient) + PayClient client = mock(PayClient.class); + when(channelService.getPayClient(eq(10L))).thenReturn(client); + // mock 方法(PayClient 异常) + when(client.getOrder(any())).thenThrow(new RuntimeException()); + + // 调用 + int count = orderService.syncOrder(minCreateTime); + // 断言 + assertEquals(count, 0); + } + + @Test + public void testSyncOrder_orderSuccess() { + PayOrderServiceImpl payOrderServiceImpl = mock(PayOrderServiceImpl.class); + try (MockedStatic springUtilMockedStatic = mockStatic(SpringUtil.class)) { + springUtilMockedStatic.when(() -> SpringUtil.getBean(eq(PayOrderServiceImpl.class))) + .thenReturn(payOrderServiceImpl); + + // 准备参数 + LocalDateTime minCreateTime = LocalDateTime.now().minus(Duration.ofMinutes(10)); + // mock 数据(PayOrderExtensionDO) + PayOrderExtensionDO orderExtension = randomPojo(PayOrderExtensionDO.class, + o -> o.setStatus(PayOrderStatusEnum.WAITING.getStatus()) + .setChannelId(10L).setNo("P110") + .setCreateTime(LocalDateTime.now())); + orderExtensionMapper.insert(orderExtension); + // mock 方法(PayClient) + PayClient client = mock(PayClient.class); + when(channelService.getPayClient(eq(10L))).thenReturn(client); + // mock 方法(PayClient 成功返回) + PayOrderRespDTO respDTO = randomPojo(PayOrderRespDTO.class, + o -> o.setStatus(PayOrderStatusEnum.SUCCESS.getStatus())); + when(client.getOrder(eq("P110"))).thenReturn(respDTO); + // mock 方法(PayChannelDO) + PayChannelDO channel = randomPojo(PayChannelDO.class, o -> o.setId(10L)); + when(channelService.validPayChannel(eq(10L))).thenReturn(channel); + + // 调用 + int count = orderService.syncOrder(minCreateTime); + // 断言 + assertEquals(count, 1); + verify(payOrderServiceImpl).notifyOrder(same(channel), same(respDTO)); + } + } + + @Test + public void testSyncOrder_orderClosed() { + PayOrderServiceImpl payOrderServiceImpl = mock(PayOrderServiceImpl.class); + try (MockedStatic springUtilMockedStatic = mockStatic(SpringUtil.class)) { + springUtilMockedStatic.when(() -> SpringUtil.getBean(eq(PayOrderServiceImpl.class))) + .thenReturn(payOrderServiceImpl); + + // 准备参数 + LocalDateTime minCreateTime = LocalDateTime.now().minus(Duration.ofMinutes(10)); + // mock 数据(PayOrderExtensionDO) + PayOrderExtensionDO orderExtension = randomPojo(PayOrderExtensionDO.class, + o -> o.setStatus(PayOrderStatusEnum.WAITING.getStatus()) + .setChannelId(10L).setNo("P110") + .setCreateTime(LocalDateTime.now())); + orderExtensionMapper.insert(orderExtension); + // mock 方法(PayClient) + PayClient client = mock(PayClient.class); + when(channelService.getPayClient(eq(10L))).thenReturn(client); + // mock 方法(PayClient 成功返回) + PayOrderRespDTO respDTO = randomPojo(PayOrderRespDTO.class, + o -> o.setStatus(PayOrderStatusEnum.CLOSED.getStatus())); + when(client.getOrder(eq("P110"))).thenReturn(respDTO); + // mock 方法(PayChannelDO) + PayChannelDO channel = randomPojo(PayChannelDO.class, o -> o.setId(10L)); + when(channelService.validPayChannel(eq(10L))).thenReturn(channel); + + // 调用 + int count = orderService.syncOrder(minCreateTime); + // 断言 + assertEquals(count, 0); + verify(payOrderServiceImpl, never()).notifyOrder(same(channel), same(respDTO)); + } + } + + @Test + public void testExpireOrder_orderExtension_isSuccess() { + // mock 数据(PayOrderDO) + PayOrderDO order = randomPojo(PayOrderDO.class, + o -> o.setStatus(PayOrderStatusEnum.WAITING.getStatus()) + .setExpireTime(addTime(Duration.ofMinutes(-1)))); + orderMapper.insert(order); + // mock 数据(PayOrderExtensionDO 已支付) + PayOrderExtensionDO orderExtension = randomPojo(PayOrderExtensionDO.class, + o -> o.setStatus(PayOrderStatusEnum.SUCCESS.getStatus()) + .setOrderId(order.getId())); + orderExtensionMapper.insert(orderExtension); + // mock 方法(PayClient) + PayClient client = mock(PayClient.class); + when(channelService.getPayClient(eq(10L))).thenReturn(client); + + // 调用 + int count = orderService.expireOrder(); + // 断言 + assertEquals(count, 0); + // 断言 order 没有变化,因为没更新 + assertPojoEquals(order, orderMapper.selectOne(null)); + } + + @Test + public void testExpireOrder_payClient_notFound() { + // mock 数据(PayOrderDO) + PayOrderDO order = randomPojo(PayOrderDO.class, + o -> o.setStatus(PayOrderStatusEnum.WAITING.getStatus()) + .setExpireTime(addTime(Duration.ofMinutes(-1)))); + orderMapper.insert(order); + // mock 数据(PayOrderExtensionDO 等待中) + PayOrderExtensionDO orderExtension = randomPojo(PayOrderExtensionDO.class, + o -> o.setStatus(PayOrderStatusEnum.WAITING.getStatus()) + .setOrderId(order.getId()) + .setChannelId(10L)); + orderExtensionMapper.insert(orderExtension); + + // 调用 + int count = orderService.expireOrder(); + // 断言 + assertEquals(count, 0); + // 断言 order 没有变化,因为没更新 + assertPojoEquals(order, orderMapper.selectOne(null)); + } + + @Test + public void testExpireOrder_getOrder_isRefund() { + // mock 数据(PayOrderDO) + PayOrderDO order = randomPojo(PayOrderDO.class, + o -> o.setStatus(PayOrderStatusEnum.WAITING.getStatus()) + .setExpireTime(addTime(Duration.ofMinutes(-1)))); + orderMapper.insert(order); + // mock 数据(PayOrderExtensionDO 等待中) + PayOrderExtensionDO orderExtension = randomPojo(PayOrderExtensionDO.class, + o -> o.setStatus(PayOrderStatusEnum.WAITING.getStatus()) + .setOrderId(order.getId()).setNo("P110") + .setChannelId(10L)); + orderExtensionMapper.insert(orderExtension); + // mock 方法(PayClient) + PayClient client = mock(PayClient.class); + when(channelService.getPayClient(eq(10L))).thenReturn(client); + // mock 方法(PayClient 退款返回) + PayOrderRespDTO respDTO = randomPojo(PayOrderRespDTO.class, + o -> o.setStatus(PayOrderStatusEnum.REFUND.getStatus())); + when(client.getOrder(eq("P110"))).thenReturn(respDTO); + + // 调用 + int count = orderService.expireOrder(); + // 断言 + assertEquals(count, 0); + // 断言 order 没有变化,因为没更新 + assertPojoEquals(order, orderMapper.selectOne(null)); + } + + @Test + public void testExpireOrder_getOrder_isSuccess() { + PayOrderServiceImpl payOrderServiceImpl = mock(PayOrderServiceImpl.class); + try (MockedStatic springUtilMockedStatic = mockStatic(SpringUtil.class)) { + springUtilMockedStatic.when(() -> SpringUtil.getBean(eq(PayOrderServiceImpl.class))) + .thenReturn(payOrderServiceImpl); + + // mock 数据(PayOrderDO) + PayOrderDO order = randomPojo(PayOrderDO.class, + o -> o.setStatus(PayOrderStatusEnum.WAITING.getStatus()) + .setExpireTime(addTime(Duration.ofMinutes(-1)))); + orderMapper.insert(order); + // mock 数据(PayOrderExtensionDO 等待中) + PayOrderExtensionDO orderExtension = randomPojo(PayOrderExtensionDO.class, + o -> o.setStatus(PayOrderStatusEnum.WAITING.getStatus()) + .setOrderId(order.getId()).setNo("P110") + .setChannelId(10L)); + orderExtensionMapper.insert(orderExtension); + // mock 方法(PayClient) + PayClient client = mock(PayClient.class); + when(channelService.getPayClient(eq(10L))).thenReturn(client); + // mock 方法(PayClient 成功返回) + PayOrderRespDTO respDTO = randomPojo(PayOrderRespDTO.class, + o -> o.setStatus(PayOrderStatusEnum.SUCCESS.getStatus())); + when(client.getOrder(eq("P110"))).thenReturn(respDTO); + // mock 方法(PayChannelDO) + PayChannelDO channel = randomPojo(PayChannelDO.class, o -> o.setId(10L)); + when(channelService.validPayChannel(eq(10L))).thenReturn(channel); + + // 调用 + int count = orderService.expireOrder(); + // 断言 + assertEquals(count, 0); + // 断言 order 没有变化,因为没更新 + assertPojoEquals(order, orderMapper.selectOne(null)); + verify(payOrderServiceImpl).notifyOrder(same(channel), same(respDTO)); + } + } + + @Test + public void testExpireOrder_success() { + // mock 数据(PayOrderDO) + PayOrderDO order = randomPojo(PayOrderDO.class, + o -> o.setStatus(PayOrderStatusEnum.WAITING.getStatus()) + .setExpireTime(addTime(Duration.ofMinutes(-1)))); + orderMapper.insert(order); + // mock 数据(PayOrderExtensionDO 等待中) + PayOrderExtensionDO orderExtension = randomPojo(PayOrderExtensionDO.class, + o -> o.setStatus(PayOrderStatusEnum.WAITING.getStatus()) + .setOrderId(order.getId()).setNo("P110") + .setChannelId(10L)); + orderExtensionMapper.insert(orderExtension); + // mock 方法(PayClient) + PayClient client = mock(PayClient.class); + when(channelService.getPayClient(eq(10L))).thenReturn(client); + // mock 方法(PayClient 关闭返回) + PayOrderRespDTO respDTO = randomPojo(PayOrderRespDTO.class, + o -> o.setStatus(PayOrderStatusEnum.CLOSED.getStatus())); + when(client.getOrder(eq("P110"))).thenReturn(respDTO); + + // 调用 + int count = orderService.expireOrder(); + // 断言 + assertEquals(count, 1); + // 断言 extension 变化 + orderExtension.setStatus(PayOrderStatusEnum.CLOSED.getStatus()) + .setChannelNotifyData(toJsonString(respDTO)); + assertPojoEquals(orderExtension, orderExtensionMapper.selectOne(null), + "updateTime", "updater"); + // 断言 order 变化 + order.setStatus(PayOrderStatusEnum.CLOSED.getStatus()); + assertPojoEquals(order, orderMapper.selectOne(null), + "updateTime", "updater"); + } + +} diff --git a/yudao-module-pay/src/test/java/cn/iocoder/yudao/module/pay/service/refund/PayRefundServiceTest.java b/yudao-module-pay/src/test/java/cn/iocoder/yudao/module/pay/service/refund/PayRefundServiceTest.java new file mode 100755 index 0000000..87c4cdb --- /dev/null +++ b/yudao-module-pay/src/test/java/cn/iocoder/yudao/module/pay/service/refund/PayRefundServiceTest.java @@ -0,0 +1,699 @@ +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.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; +import cn.iocoder.yudao.module.pay.controller.admin.refund.vo.PayRefundPageReqVO; +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; +import cn.iocoder.yudao.module.pay.dal.dataobject.refund.PayRefundDO; +import cn.iocoder.yudao.module.pay.dal.mysql.refund.PayRefundMapper; +import cn.iocoder.yudao.module.pay.dal.redis.no.PayNoRedisDAO; +import cn.iocoder.yudao.module.pay.enums.notify.PayNotifyTypeEnum; +import cn.iocoder.yudao.module.pay.enums.order.PayOrderStatusEnum; +import cn.iocoder.yudao.module.pay.enums.refund.PayRefundStatusEnum; +import cn.iocoder.yudao.module.pay.framework.pay.config.PayProperties; +import cn.iocoder.yudao.module.pay.service.app.PayAppService; +import cn.iocoder.yudao.module.pay.service.channel.PayChannelService; +import cn.iocoder.yudao.module.pay.service.notify.PayNotifyService; +import cn.iocoder.yudao.module.pay.service.order.PayOrderService; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.MockedStatic; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.context.annotation.Import; + +import jakarta.annotation.Resource; +import java.util.List; + +import static cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.buildBetweenTime; +import static cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.buildTime; +import static cn.iocoder.yudao.framework.common.util.json.JsonUtils.toJsonString; +import static cn.iocoder.yudao.framework.common.util.object.ObjectUtils.cloneIgnoreId; +import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertPojoEquals; +import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertServiceException; +import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo; +import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomString; +import static cn.iocoder.yudao.module.pay.enums.ErrorCodeConstants.*; +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.*; + +/** + * {@link PayRefundServiceImpl} 的单元测试类 + * + * @author 芋艿 + */ +@Import({PayRefundServiceImpl.class, PayNoRedisDAO.class}) +public class PayRefundServiceTest extends BaseDbAndRedisUnitTest { + + @Resource + private PayRefundServiceImpl refundService; + + @Resource + private PayRefundMapper refundMapper; + + @MockBean + private PayProperties payProperties; + @MockBean + private PayOrderService orderService; + @MockBean + private PayAppService appService; + @MockBean + private PayChannelService channelService; + @MockBean + private PayNotifyService notifyService; + + @BeforeEach + public void setUp() { + when(payProperties.getRefundNotifyUrl()).thenReturn("http://127.0.0.1"); + } + + @Test + public void testGetRefund() { + // mock 数据 + PayRefundDO refund = randomPojo(PayRefundDO.class); + refundMapper.insert(refund); + // 准备参数 + Long id = refund.getId(); + + // 调用 + PayRefundDO dbRefund = refundService.getRefund(id); + // 断言 + assertPojoEquals(dbRefund, refund); + } + + @Test + public void testGetRefundCountByAppId() { + // mock 数据 + PayRefundDO refund01 = randomPojo(PayRefundDO.class); + refundMapper.insert(refund01); + PayRefundDO refund02 = randomPojo(PayRefundDO.class); + refundMapper.insert(refund02); + // 准备参数 + Long appId = refund01.getAppId(); + + // 调用 + Long count = refundService.getRefundCountByAppId(appId); + // 断言 + assertEquals(count, 1); + } + + @Test + public void testGetRefundPage() { + // mock 数据 + PayRefundDO dbRefund = randomPojo(PayRefundDO.class, o -> { // 等会查询到 + o.setAppId(1L); + o.setChannelCode(PayChannelEnum.WX_PUB.getCode()); + o.setMerchantOrderId("MOT0000001"); + o.setMerchantRefundId("MRF0000001"); + o.setStatus(PayOrderStatusEnum.SUCCESS.getStatus()); + o.setChannelOrderNo("CH0000001"); + o.setChannelRefundNo("CHR0000001"); + o.setCreateTime(buildTime(2021, 1, 10)); + }); + refundMapper.insert(dbRefund); + // 测试 appId 不匹配 + refundMapper.insert(cloneIgnoreId(dbRefund, o -> o.setAppId(2L))); + // 测试 channelCode 不匹配 + refundMapper.insert(cloneIgnoreId(dbRefund, o -> o.setChannelCode(PayChannelEnum.ALIPAY_APP.getCode()))); + // 测试 merchantOrderId 不匹配 + refundMapper.insert(cloneIgnoreId(dbRefund, o -> o.setMerchantOrderId(randomString()))); + // 测试 merchantRefundId 不匹配 + refundMapper.insert(cloneIgnoreId(dbRefund, o -> o.setMerchantRefundId(randomString()))); + // 测试 channelOrderNo 不匹配 + refundMapper.insert(cloneIgnoreId(dbRefund, o -> o.setChannelOrderNo(randomString()))); + // 测试 channelRefundNo 不匹配 + refundMapper.insert(cloneIgnoreId(dbRefund, o -> o.setChannelRefundNo(randomString()))); + // 测试 status 不匹配 + refundMapper.insert(cloneIgnoreId(dbRefund, o -> o.setStatus(PayOrderStatusEnum.WAITING.getStatus()))); + // 测试 createTime 不匹配 + refundMapper.insert(cloneIgnoreId(dbRefund, o -> o.setCreateTime(buildTime(2021, 1, 1)))); + // 准备参数 + PayRefundPageReqVO reqVO = new PayRefundPageReqVO(); + reqVO.setAppId(1L); + reqVO.setChannelCode(PayChannelEnum.WX_PUB.getCode()); + reqVO.setMerchantOrderId("MOT0000001"); + reqVO.setMerchantRefundId("MRF0000001"); + reqVO.setStatus(PayOrderStatusEnum.SUCCESS.getStatus()); + reqVO.setChannelOrderNo("CH0000001"); + reqVO.setChannelRefundNo("CHR0000001"); + reqVO.setCreateTime(buildBetweenTime(2021, 1, 9, 2021, 1, 11)); + + // 调用 + PageResult pageResult = refundService.getRefundPage(reqVO); + // 断言 + assertEquals(1, pageResult.getTotal()); + assertEquals(1, pageResult.getList().size()); + assertPojoEquals(dbRefund, pageResult.getList().get(0)); + } + + @Test + public void testGetRefundList() { + // mock 数据 + PayRefundDO dbRefund = randomPojo(PayRefundDO.class, o -> { // 等会查询到 + o.setAppId(1L); + o.setChannelCode(PayChannelEnum.WX_PUB.getCode()); + o.setMerchantOrderId("MOT0000001"); + o.setMerchantRefundId("MRF0000001"); + o.setStatus(PayOrderStatusEnum.SUCCESS.getStatus()); + o.setChannelOrderNo("CH0000001"); + o.setChannelRefundNo("CHR0000001"); + o.setCreateTime(buildTime(2021, 1, 10)); + }); + refundMapper.insert(dbRefund); + // 测试 appId 不匹配 + refundMapper.insert(cloneIgnoreId(dbRefund, o -> o.setAppId(2L))); + // 测试 channelCode 不匹配 + refundMapper.insert(cloneIgnoreId(dbRefund, o -> o.setChannelCode(PayChannelEnum.ALIPAY_APP.getCode()))); + // 测试 merchantOrderId 不匹配 + refundMapper.insert(cloneIgnoreId(dbRefund, o -> o.setMerchantOrderId(randomString()))); + // 测试 merchantRefundId 不匹配 + refundMapper.insert(cloneIgnoreId(dbRefund, o -> o.setMerchantRefundId(randomString()))); + // 测试 channelOrderNo 不匹配 + refundMapper.insert(cloneIgnoreId(dbRefund, o -> o.setChannelOrderNo(randomString()))); + // 测试 channelRefundNo 不匹配 + refundMapper.insert(cloneIgnoreId(dbRefund, o -> o.setChannelRefundNo(randomString()))); + // 测试 status 不匹配 + refundMapper.insert(cloneIgnoreId(dbRefund, o -> o.setStatus(PayOrderStatusEnum.WAITING.getStatus()))); + // 测试 createTime 不匹配 + refundMapper.insert(cloneIgnoreId(dbRefund, o -> o.setCreateTime(buildTime(2021, 1, 1)))); + // 准备参数 + PayRefundExportReqVO reqVO = new PayRefundExportReqVO(); + reqVO.setAppId(1L); + reqVO.setChannelCode(PayChannelEnum.WX_PUB.getCode()); + reqVO.setMerchantOrderId("MOT0000001"); + reqVO.setMerchantRefundId("MRF0000001"); + reqVO.setStatus(PayOrderStatusEnum.SUCCESS.getStatus()); + reqVO.setChannelOrderNo("CH0000001"); + reqVO.setChannelRefundNo("CHR0000001"); + reqVO.setCreateTime(buildBetweenTime(2021, 1, 9, 2021, 1, 11)); + + // 调用 + List list = refundService.getRefundList(reqVO); + // 断言 + assertEquals(1, list.size()); + assertPojoEquals(dbRefund, list.get(0)); + } + + @Test + public void testCreateRefund_orderNotFound() { + PayRefundCreateReqDTO reqDTO = randomPojo(PayRefundCreateReqDTO.class, + o -> o.setAppKey("demo")); + // mock 方法(app) + PayAppDO app = randomPojo(PayAppDO.class, o -> o.setId(1L)); + when(appService.validPayApp(eq("demo"))).thenReturn(app); + + // 调用,并断言异常 + assertServiceException(() -> refundService.createRefund(reqDTO), + PAY_ORDER_NOT_FOUND); + } + + @Test + public void testCreateRefund_orderWaiting() { + testCreateRefund_orderWaitingOrClosed(PayOrderStatusEnum.WAITING.getStatus()); + } + + @Test + public void testCreateRefund_orderClosed() { + testCreateRefund_orderWaitingOrClosed(PayOrderStatusEnum.CLOSED.getStatus()); + } + + private void testCreateRefund_orderWaitingOrClosed(Integer status) { + // 准备参数 + PayRefundCreateReqDTO reqDTO = randomPojo(PayRefundCreateReqDTO.class, + o -> o.setAppKey("demo").setMerchantOrderId("100")); + // mock 方法(app) + PayAppDO app = randomPojo(PayAppDO.class, o -> o.setId(1L)); + when(appService.validPayApp(eq("demo"))).thenReturn(app); + // mock 数据(order) + PayOrderDO order = randomPojo(PayOrderDO.class, o -> o.setStatus(status)); + when(orderService.getOrder(eq(1L), eq("100"))).thenReturn(order); + + // 调用,并断言异常 + assertServiceException(() -> refundService.createRefund(reqDTO), + PAY_ORDER_REFUND_FAIL_STATUS_ERROR); + } + + @Test + public void testCreateRefund_refundPriceExceed() { + // 准备参数 + PayRefundCreateReqDTO reqDTO = randomPojo(PayRefundCreateReqDTO.class, + o -> o.setAppKey("demo").setMerchantOrderId("100").setPrice(10)); + // mock 方法(app) + PayAppDO app = randomPojo(PayAppDO.class, o -> o.setId(1L)); + when(appService.validPayApp(eq("demo"))).thenReturn(app); + // mock 数据(order) + PayOrderDO order = randomPojo(PayOrderDO.class, o -> + o.setStatus(PayOrderStatusEnum.REFUND.getStatus()) + .setPrice(10).setRefundPrice(1)); + when(orderService.getOrder(eq(1L), eq("100"))).thenReturn(order); + + // 调用,并断言异常 + assertServiceException(() -> refundService.createRefund(reqDTO), + REFUND_PRICE_EXCEED); + } + + @Test + public void testCreateRefund_orderHasRefunding() { + // 准备参数 + PayRefundCreateReqDTO reqDTO = randomPojo(PayRefundCreateReqDTO.class, + o -> o.setAppKey("demo").setMerchantOrderId("100").setPrice(10)); + // mock 方法(app) + PayAppDO app = randomPojo(PayAppDO.class, o -> o.setId(1L)); + when(appService.validPayApp(eq("demo"))).thenReturn(app); + // mock 数据(order) + PayOrderDO order = randomPojo(PayOrderDO.class, o -> + o.setStatus(PayOrderStatusEnum.REFUND.getStatus()) + .setPrice(10).setRefundPrice(1)); + when(orderService.getOrder(eq(1L), eq("100"))).thenReturn(order); + // mock 数据(refund 在退款中) + PayRefundDO refund = randomPojo(PayRefundDO.class, o -> + o.setOrderId(order.getId()).setStatus(PayOrderStatusEnum.WAITING.getStatus())); + refundMapper.insert(refund); + + // 调用,并断言异常 + assertServiceException(() -> refundService.createRefund(reqDTO), + REFUND_PRICE_EXCEED); + } + + @Test + public void testCreateRefund_channelNotFound() { + // 准备参数 + PayRefundCreateReqDTO reqDTO = randomPojo(PayRefundCreateReqDTO.class, + o -> o.setAppKey("demo").setMerchantOrderId("100").setPrice(9)); + // mock 方法(app) + PayAppDO app = randomPojo(PayAppDO.class, o -> o.setId(1L)); + when(appService.validPayApp(eq("demo"))).thenReturn(app); + // mock 数据(order) + PayOrderDO order = randomPojo(PayOrderDO.class, o -> + o.setStatus(PayOrderStatusEnum.REFUND.getStatus()) + .setPrice(10).setRefundPrice(1) + .setChannelId(1L).setChannelCode(PayChannelEnum.ALIPAY_APP.getCode())); + when(orderService.getOrder(eq(1L), eq("100"))).thenReturn(order); + // mock 方法(channel) + PayChannelDO channel = randomPojo(PayChannelDO.class, o -> o.setId(10L) + .setCode(PayChannelEnum.ALIPAY_APP.getCode())); + when(channelService.validPayChannel(eq(1L))).thenReturn(channel); + + // 调用,并断言异常 + assertServiceException(() -> refundService.createRefund(reqDTO), + CHANNEL_NOT_FOUND); + } + + @Test + public void testCreateRefund_refundExists() { + // 准备参数 + PayRefundCreateReqDTO reqDTO = randomPojo(PayRefundCreateReqDTO.class, + o -> o.setAppKey("demo").setMerchantOrderId("100").setPrice(9) + .setMerchantRefundId("200").setReason("测试退款")); + // mock 方法(app) + PayAppDO app = randomPojo(PayAppDO.class, o -> o.setId(1L)); + when(appService.validPayApp(eq("demo"))).thenReturn(app); + // mock 数据(order) + PayOrderDO order = randomPojo(PayOrderDO.class, o -> + o.setStatus(PayOrderStatusEnum.REFUND.getStatus()) + .setPrice(10).setRefundPrice(1) + .setChannelId(1L).setChannelCode(PayChannelEnum.ALIPAY_APP.getCode())); + when(orderService.getOrder(eq(1L), eq("100"))).thenReturn(order); + // mock 方法(channel) + PayChannelDO channel = randomPojo(PayChannelDO.class, o -> o.setId(10L) + .setCode(PayChannelEnum.ALIPAY_APP.getCode())); + when(channelService.validPayChannel(eq(1L))).thenReturn(channel); + // mock 方法(client) + PayClient client = mock(PayClient.class); + when(channelService.getPayClient(eq(10L))).thenReturn(client); + // mock 数据(refund 已存在) + PayRefundDO refund = randomPojo(PayRefundDO.class, o -> + o.setAppId(1L).setMerchantRefundId("200")); + refundMapper.insert(refund); + + // 调用,并断言异常 + assertServiceException(() -> refundService.createRefund(reqDTO), + REFUND_EXISTS); + } + + @Test + public void testCreateRefund_invokeException() { + // 准备参数 + PayRefundCreateReqDTO reqDTO = randomPojo(PayRefundCreateReqDTO.class, + o -> o.setAppKey("demo").setMerchantOrderId("100").setPrice(9) + .setMerchantRefundId("200").setReason("测试退款")); + // mock 方法(app) + PayAppDO app = randomPojo(PayAppDO.class, o -> o.setId(1L)); + when(appService.validPayApp(eq("demo"))).thenReturn(app); + // mock 数据(order) + PayOrderDO order = randomPojo(PayOrderDO.class, o -> + o.setStatus(PayOrderStatusEnum.REFUND.getStatus()) + .setPrice(10).setRefundPrice(1) + .setChannelId(10L).setChannelCode(PayChannelEnum.ALIPAY_APP.getCode())); + when(orderService.getOrder(eq(1L), eq("100"))).thenReturn(order); + // mock 方法(channel) + PayChannelDO channel = randomPojo(PayChannelDO.class, o -> o.setId(10L) + .setCode(PayChannelEnum.ALIPAY_APP.getCode())); + when(channelService.validPayChannel(eq(10L))).thenReturn(channel); + // mock 方法(client) + PayClient client = mock(PayClient.class); + when(channelService.getPayClient(eq(10L))).thenReturn(client); + // mock 方法(client 调用发生异常) + when(client.unifiedRefund(any(PayRefundUnifiedReqDTO.class))).thenThrow(new RuntimeException()); + + // 调用 + Long refundId = refundService.createRefund(reqDTO); + // 断言 + PayRefundDO refundDO = refundMapper.selectById(refundId); + assertPojoEquals(reqDTO, refundDO); + assertNotNull(refundDO.getNo()); + assertThat(refundDO) + .extracting("orderId", "orderNo", "channelId", "channelCode", + "notifyUrl", "channelOrderNo", "status", "payPrice", "refundPrice") + .containsExactly(order.getId(), order.getNo(), channel.getId(), channel.getCode(), + app.getRefundNotifyUrl(), order.getChannelOrderNo(), PayRefundStatusEnum.WAITING.getStatus(), + order.getPrice(), reqDTO.getPrice()); + } + + @Test + public void testCreateRefund_invokeSuccess() { + PayRefundServiceImpl payRefundServiceImpl = mock(PayRefundServiceImpl.class); + try (MockedStatic springUtilMockedStatic = mockStatic(SpringUtil.class)) { + springUtilMockedStatic.when(() -> SpringUtil.getBean(eq(PayRefundServiceImpl.class))) + .thenReturn(payRefundServiceImpl); + + // 准备参数 + PayRefundCreateReqDTO reqDTO = randomPojo(PayRefundCreateReqDTO.class, + o -> o.setAppKey("demo").setMerchantOrderId("100").setPrice(9) + .setMerchantRefundId("200").setReason("测试退款")); + // mock 方法(app) + PayAppDO app = randomPojo(PayAppDO.class, o -> o.setId(1L)); + when(appService.validPayApp(eq("demo"))).thenReturn(app); + // mock 数据(order) + PayOrderDO order = randomPojo(PayOrderDO.class, o -> + o.setStatus(PayOrderStatusEnum.REFUND.getStatus()) + .setPrice(10).setRefundPrice(1) + .setChannelId(10L).setChannelCode(PayChannelEnum.ALIPAY_APP.getCode())); + when(orderService.getOrder(eq(1L), eq("100"))).thenReturn(order); + // mock 方法(channel) + PayChannelDO channel = randomPojo(PayChannelDO.class, o -> o.setId(10L) + .setCode(PayChannelEnum.ALIPAY_APP.getCode())); + when(channelService.validPayChannel(eq(10L))).thenReturn(channel); + // mock 方法(client) + PayClient client = mock(PayClient.class); + when(channelService.getPayClient(eq(10L))).thenReturn(client); + // mock 方法(client 成功) + PayRefundRespDTO refundRespDTO = randomPojo(PayRefundRespDTO.class); + when(client.unifiedRefund(argThat(unifiedReqDTO -> { + assertNotNull(unifiedReqDTO.getOutRefundNo()); + assertThat(unifiedReqDTO) + .extracting("payPrice", "refundPrice", "outTradeNo", + "notifyUrl", "reason") + .containsExactly(order.getPrice(), reqDTO.getPrice(), order.getNo(), + "http://127.0.0.1/10", reqDTO.getReason()); + return true; + }))).thenReturn(refundRespDTO); + + // 调用 + Long refundId = refundService.createRefund(reqDTO); + // 断言 + PayRefundDO refundDO = refundMapper.selectById(refundId); + assertPojoEquals(reqDTO, refundDO); + assertNotNull(refundDO.getNo()); + assertThat(refundDO) + .extracting("orderId", "orderNo", "channelId", "channelCode", + "notifyUrl", "channelOrderNo", "status", "payPrice", "refundPrice") + .containsExactly(order.getId(), order.getNo(), channel.getId(), channel.getCode(), + app.getRefundNotifyUrl(), order.getChannelOrderNo(), PayRefundStatusEnum.WAITING.getStatus(), + order.getPrice(), reqDTO.getPrice()); + // 断言调用 + verify(payRefundServiceImpl).notifyRefund(same(channel), same(refundRespDTO)); + } + } + + @Test + public void testNotifyRefund() { + PayRefundServiceImpl payRefundServiceImpl = mock(PayRefundServiceImpl.class); + try (MockedStatic springUtilMockedStatic = mockStatic(SpringUtil.class)) { + springUtilMockedStatic.when(() -> SpringUtil.getBean(eq(PayRefundServiceImpl.class))) + .thenReturn(payRefundServiceImpl); + + // 准备参数 + Long channelId = 10L; + PayRefundRespDTO refundRespDTO = randomPojo(PayRefundRespDTO.class); + // mock 方法(channel) + PayChannelDO channel = randomPojo(PayChannelDO.class, o -> o.setId(10L)); + when(channelService.validPayChannel(eq(10L))).thenReturn(channel); + + // 调用 + refundService.notifyRefund(channelId, refundRespDTO); + // 断言 + verify(payRefundServiceImpl).notifyRefund(same(channel), same(refundRespDTO)); + } + } + + @Test + public void testNotifyRefundSuccess_notFound() { + // 准备参数 + PayChannelDO channel = randomPojo(PayChannelDO.class, o -> o.setId(10L).setAppId(1L)); + PayRefundRespDTO refundRespDTO = randomPojo(PayRefundRespDTO.class, + o -> o.setStatus(PayRefundStatusEnum.SUCCESS.getStatus()).setOutRefundNo("R100")); + + // 调用,并断言异常 + assertServiceException(() -> refundService.notifyRefund(channel, refundRespDTO), + REFUND_NOT_FOUND); + } + + @Test + public void testNotifyRefundSuccess_isSuccess() { + // 准备参数 + PayChannelDO channel = randomPojo(PayChannelDO.class, o -> o.setId(10L).setAppId(1L)); + PayRefundRespDTO refundRespDTO = randomPojo(PayRefundRespDTO.class, + 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())); + refundMapper.insert(refund); + + // 调用 + refundService.notifyRefund(channel, refundRespDTO); + // 断言,refund 没有更新,因为已经退款成功 + assertPojoEquals(refund, refundMapper.selectById(refund.getId())); + } + + @Test + public void testNotifyRefundSuccess_failure() { + // 准备参数 + PayChannelDO channel = randomPojo(PayChannelDO.class, o -> o.setId(10L).setAppId(1L)); + PayRefundRespDTO refundRespDTO = randomPojo(PayRefundRespDTO.class, + 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())); + refundMapper.insert(refund); + + // 调用,并断言异常 + assertServiceException(() -> refundService.notifyRefund(channel, refundRespDTO), + REFUND_STATUS_IS_NOT_WAITING); + } + + @Test + public void testNotifyRefundSuccess_updateOrderException() { + // 准备参数 + PayChannelDO channel = randomPojo(PayChannelDO.class, o -> o.setId(10L).setAppId(1L)); + PayRefundRespDTO refundRespDTO = randomPojo(PayRefundRespDTO.class, + 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()) + .setOrderId(100L).setRefundPrice(23)); + refundMapper.insert(refund); + // mock 方法(order + 更新异常) + doThrow(new RuntimeException()).when(orderService) + .updateOrderRefundPrice(eq(100L), eq(23)); + + // 调用,并断言异常 + assertThrows(RuntimeException.class, () -> refundService.notifyRefund(channel, refundRespDTO)); + // 断言,refund 没有更新,因为事务回滚了 + assertPojoEquals(refund, refundMapper.selectById(refund.getId())); + } + + @Test + public void testNotifyRefundSuccess_success() { + // 准备参数 + PayChannelDO channel = randomPojo(PayChannelDO.class, o -> o.setId(10L).setAppId(1L)); + PayRefundRespDTO refundRespDTO = randomPojo(PayRefundRespDTO.class, + 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()) + .setOrderId(100L).setRefundPrice(23)); + refundMapper.insert(refund); + + // 调用 + refundService.notifyRefund(channel, refundRespDTO); + // 断言,refund + refund.setSuccessTime(refundRespDTO.getSuccessTime()) + .setChannelRefundNo(refundRespDTO.getChannelRefundNo()) + .setStatus(PayRefundStatusEnum.SUCCESS.getStatus()) + .setChannelNotifyData(toJsonString(refundRespDTO)); + assertPojoEquals(refund, refundMapper.selectById(refund.getId()), + "updateTime", "updater"); + // 断言,调用 + verify(orderService).updateOrderRefundPrice(eq(100L), eq(23)); + verify(notifyService).createPayNotifyTask(eq(PayNotifyTypeEnum.REFUND.getType()), + eq(refund.getId())); + } + + @Test + public void testNotifyRefundFailure_notFound() { + // 准备参数 + PayChannelDO channel = randomPojo(PayChannelDO.class, o -> o.setId(10L).setAppId(1L)); + PayRefundRespDTO refundRespDTO = randomPojo(PayRefundRespDTO.class, + o -> o.setStatus(PayRefundStatusEnum.FAILURE.getStatus()).setOutRefundNo("R100")); + + // 调用,并断言异常 + assertServiceException(() -> refundService.notifyRefund(channel, refundRespDTO), + REFUND_NOT_FOUND); + } + + @Test + public void testNotifyRefundFailure_isFailure() { + // 准备参数 + PayChannelDO channel = randomPojo(PayChannelDO.class, o -> o.setId(10L).setAppId(1L)); + PayRefundRespDTO refundRespDTO = randomPojo(PayRefundRespDTO.class, + 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())); + refundMapper.insert(refund); + + // 调用 + refundService.notifyRefund(channel, refundRespDTO); + // 断言,refund 没有更新,因为已经退款失败 + assertPojoEquals(refund, refundMapper.selectById(refund.getId())); + } + + @Test + public void testNotifyRefundFailure_isSuccess() { + // 准备参数 + PayChannelDO channel = randomPojo(PayChannelDO.class, o -> o.setId(10L).setAppId(1L)); + PayRefundRespDTO refundRespDTO = randomPojo(PayRefundRespDTO.class, + 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())); + refundMapper.insert(refund); + + // 调用,并断言异常 + assertServiceException(() -> refundService.notifyRefund(channel, refundRespDTO), + REFUND_STATUS_IS_NOT_WAITING); + } + + @Test + public void testNotifyRefundFailure_success() { + // 准备参数 + PayChannelDO channel = randomPojo(PayChannelDO.class, o -> o.setId(10L).setAppId(1L)); + PayRefundRespDTO refundRespDTO = randomPojo(PayRefundRespDTO.class, + 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()) + .setOrderId(100L).setRefundPrice(23)); + refundMapper.insert(refund); + + // 调用 + refundService.notifyRefund(channel, refundRespDTO); + // 断言,refund + refund.setChannelRefundNo(refundRespDTO.getChannelRefundNo()) + .setStatus(PayRefundStatusEnum.FAILURE.getStatus()) + .setChannelNotifyData(toJsonString(refundRespDTO)) + .setChannelErrorCode(refundRespDTO.getChannelErrorCode()) + .setChannelErrorMsg(refundRespDTO.getChannelErrorMsg()); + assertPojoEquals(refund, refundMapper.selectById(refund.getId()), + "updateTime", "updater"); + // 断言,调用 + verify(notifyService).createPayNotifyTask(eq(PayNotifyTypeEnum.REFUND.getType()), + eq(refund.getId())); + } + + @Test + public void testSyncRefund_notFound() { + // 准备参数 + PayRefundDO refund = randomPojo(PayRefundDO.class, o -> o.setAppId(1L) + .setStatus(PayRefundStatusEnum.WAITING.getStatus())); + refundMapper.insert(refund); + + // 调用 + int count = refundService.syncRefund(); + // 断言 + assertEquals(count, 0); + } + + @Test + public void testSyncRefund_waiting() { + assertEquals(testSyncRefund_waitingOrSuccessOrFailure(PayRefundStatusEnum.WAITING.getStatus()), 0); + } + + @Test + public void testSyncRefund_success() { + assertEquals(testSyncRefund_waitingOrSuccessOrFailure(PayRefundStatusEnum.SUCCESS.getStatus()), 1); + } + + @Test + public void testSyncRefund_failure() { + assertEquals(testSyncRefund_waitingOrSuccessOrFailure(PayRefundStatusEnum.FAILURE.getStatus()), 1); + } + + private int testSyncRefund_waitingOrSuccessOrFailure(Integer status) { + PayRefundServiceImpl payRefundServiceImpl = mock(PayRefundServiceImpl.class); + try (MockedStatic springUtilMockedStatic = mockStatic(SpringUtil.class)) { + springUtilMockedStatic.when(() -> SpringUtil.getBean(eq(PayRefundServiceImpl.class))) + .thenReturn(payRefundServiceImpl); + + // 准备参数 + PayRefundDO refund = randomPojo(PayRefundDO.class, o -> o.setAppId(1L).setChannelId(10L) + .setStatus(PayRefundStatusEnum.WAITING.getStatus()) + .setOrderNo("P110").setNo("R220")); + refundMapper.insert(refund); + // mock 方法(client) + PayClient client = mock(PayClient.class); + when(channelService.getPayClient(eq(10L))).thenReturn(client); + // mock 方法(client 返回指定状态) + PayRefundRespDTO respDTO = randomPojo(PayRefundRespDTO.class, o -> o.setStatus(status)); + when(client.getRefund(eq("P110"), eq("R220"))).thenReturn(respDTO); + // mock 方法(channel) + PayChannelDO channel = randomPojo(PayChannelDO.class, o -> o.setId(10L)); + when(channelService.validPayChannel(eq(10L))).thenReturn(channel); + + // 调用 + return refundService.syncRefund(); + } + } + + @Test + public void testSyncRefund_exception() { + // 准备参数 + PayRefundDO refund = randomPojo(PayRefundDO.class, o -> o.setAppId(1L).setChannelId(10L) + .setStatus(PayRefundStatusEnum.WAITING.getStatus()) + .setOrderNo("P110").setNo("R220")); + refundMapper.insert(refund); + // mock 方法(client) + PayClient client = mock(PayClient.class); + when(channelService.getPayClient(eq(10L))).thenReturn(client); + // mock 方法(client 抛出异常) + when(client.getRefund(eq("P110"), eq("R220"))).thenThrow(new RuntimeException()); + + // 调用 + int count = refundService.syncRefund(); + // 断言 + assertEquals(count, 0); + } + +} diff --git a/yudao-module-pay/src/test/resources/application-unit-test.yaml b/yudao-module-pay/src/test/resources/application-unit-test.yaml new file mode 100644 index 0000000..f05f050 --- /dev/null +++ b/yudao-module-pay/src/test/resources/application-unit-test.yaml @@ -0,0 +1,48 @@ +spring: + main: + lazy-initialization: true # 开启懒加载,加快速度 + banner-mode: off # 单元测试,禁用 Banner + +--- #################### 数据库相关配置 #################### + +spring: + # 数据源配置项 + datasource: + name: ruoyi-vue-pro + url: jdbc:h2:mem:testdb;MODE=MYSQL;DATABASE_TO_UPPER=false;NON_KEYWORDS=value; # MODE 使用 MySQL 模式;DATABASE_TO_UPPER 配置表和字段使用小写 + driver-class-name: org.h2.Driver + username: sa + password: + druid: + async-init: true # 单元测试,异步初始化 Druid 连接池,提升启动速度 + initial-size: 1 # 单元测试,配置为 1,提升启动速度 + sql: + init: + schema-locations: classpath:/sql/create_tables.sql + + # Redis 配置。Redisson 默认的配置足够使用,一般不需要进行调优 + data: + redis: + host: 127.0.0.1 # 地址 + port: 16379 # 端口(单元测试,使用 16379 端口) + database: 0 # 数据库索引 + +mybatis: + lazy-initialization: true # 单元测试,设置 MyBatis Mapper 延迟加载,加速每个单元测试 + +--- #################### 定时任务相关配置 #################### + +--- #################### 配置中心相关配置 #################### + +--- #################### 服务保障相关配置 #################### + +# Lock4j 配置项(单元测试,禁用 Lock4j) + +--- #################### 监控相关配置 #################### + +--- #################### 芋道相关配置 #################### + +# 芋道配置项,设置当前项目所有自定义的配置 +yudao: + info: + base-package: cn.iocoder.yudao.module diff --git a/yudao-module-pay/src/test/resources/logback.xml b/yudao-module-pay/src/test/resources/logback.xml new file mode 100644 index 0000000..daf756b --- /dev/null +++ b/yudao-module-pay/src/test/resources/logback.xml @@ -0,0 +1,4 @@ + + + + diff --git a/yudao-module-pay/src/test/resources/sql/clean.sql b/yudao-module-pay/src/test/resources/sql/clean.sql new file mode 100644 index 0000000..91fff0c --- /dev/null +++ b/yudao-module-pay/src/test/resources/sql/clean.sql @@ -0,0 +1,7 @@ +DELETE FROM pay_app; +DELETE FROM pay_channel; +DELETE FROM pay_order; +DELETE FROM pay_order_extension; +DELETE FROM pay_refund; +DELETE FROM pay_notify_task; +DELETE FROM pay_notify_log; diff --git a/yudao-module-pay/src/test/resources/sql/create_tables.sql b/yudao-module-pay/src/test/resources/sql/create_tables.sql new file mode 100644 index 0000000..3f9f764 --- /dev/null +++ b/yudao-module-pay/src/test/resources/sql/create_tables.sql @@ -0,0 +1,147 @@ +CREATE TABLE IF NOT EXISTS "pay_app" ( + "id" number NOT NULL GENERATED BY DEFAULT AS IDENTITY, + "app_key" varchar(64) NOT NULL, + "name" varchar(64) NOT NULL, + "status" tinyint NOT NULL, + "remark" varchar(255) DEFAULT NULL, + `order_notify_url` varchar(1024) NOT NULL, + `refund_notify_url` varchar(1024) NOT NULL, + "creator" varchar(64) DEFAULT '', + "create_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updater" varchar(64) DEFAULT '', + "update_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + "deleted" bit(1) NOT NULL DEFAULT FALSE, + PRIMARY KEY ("id") +) COMMENT = '支付应用'; + +CREATE TABLE IF NOT EXISTS "pay_channel" ( + "id" number NOT NULL GENERATED BY DEFAULT AS IDENTITY, + "code" varchar(32) NOT NULL, + "status" tinyint(4) NOT NULL, + "remark" varchar(255) DEFAULT NULL, + "fee_rate" double NOT NULL DEFAULT 0, + "app_id" bigint(20) NOT NULL, + "config" varchar(10240) NOT NULL, + "creator" varchar(64) NULL DEFAULT '', + "create_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updater" varchar(64) NULL DEFAULT '', + "update_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + "deleted" bit(1) NOT NULL DEFAULT FALSE, + "tenant_id" bigint not null default '0', + PRIMARY KEY ("id") +) COMMENT = '支付渠道'; + +CREATE TABLE IF NOT EXISTS `pay_order` ( + "id" number NOT NULL GENERATED BY DEFAULT AS IDENTITY, + `app_id` bigint(20) NOT NULL, + `channel_id` bigint(20) DEFAULT NULL, + `channel_code` varchar(32) DEFAULT NULL, + `merchant_order_id` varchar(64) NOT NULL, + `subject` varchar(32) NOT NULL, + `body` varchar(128) NOT NULL, + `notify_url` varchar(1024) NOT NULL, + `price` bigint(20) NOT NULL, + `channel_fee_rate` double DEFAULT 0, + `channel_fee_price` bigint(20) DEFAULT 0, + `status` tinyint(4) NOT NULL, + `user_ip` varchar(50) NOT NULL, + `expire_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + `success_time` datetime(0) DEFAULT CURRENT_TIMESTAMP, + `notify_time` datetime(0) DEFAULT CURRENT_TIMESTAMP, + `extension_id` bigint(20) DEFAULT NULL, + `no` varchar(64) NULL, + `refund_price` bigint(20) NOT NULL, + `channel_user_id` varchar(255) DEFAULT NULL, + `channel_order_no` varchar(64) DEFAULT NULL, + `creator` varchar(64) DEFAULT '', + `create_time` datetime(0) NOT NULL DEFAULT CURRENT_TIMESTAMP, + `updater` varchar(64) DEFAULT '', + `update_time` datetime(0) NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + `deleted` bit(1) NOT NULL DEFAULT FALSE, + PRIMARY KEY ("id") +) COMMENT = '支付订单'; + +CREATE TABLE IF NOT EXISTS `pay_order_extension` ( + "id" number NOT NULL GENERATED BY DEFAULT AS IDENTITY, + `no` varchar(64) NOT NULL, + `order_id` bigint(20) NOT NULL, + `channel_id` bigint(20) NOT NULL, + `channel_code` varchar(32) NOT NULL, + `user_ip` varchar(50) NULL DEFAULT NULL, + `status` tinyint(4) NOT NULL, + `channel_extras` varchar(1024) NULL DEFAULT NULL, + `channel_error_code` varchar(64) NULL, + `channel_error_msg` varchar(64) NULL, + `channel_notify_data` varchar(1024) NULL, + `creator` varchar(64) NULL DEFAULT '', + `create_time` datetime(0) NOT NULL DEFAULT CURRENT_TIMESTAMP, + `updater` varchar(64) NULL DEFAULT '', + `update_time` datetime(0) NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + `deleted` bit(1) NOT NULL DEFAULT FALSE, + PRIMARY KEY ("id") +) COMMENT = '支付订单拓展'; + +CREATE TABLE IF NOT EXISTS `pay_refund` ( + "id" number NOT NULL GENERATED BY DEFAULT AS IDENTITY, + `no` varchar(64) NOT NULL, + `app_id` bigint(20) NOT NULL, + `channel_id` bigint(20) NOT NULL, + `channel_code` varchar(32) NOT NULL, + `order_id` bigint(20) NOT NULL, + `order_no` varchar(64) NOT NULL, + `merchant_order_id` varchar(64) NOT NULL, + `merchant_refund_id` varchar(64) NOT NULL, + `notify_url` varchar(1024) NOT NULL, + `status` tinyint(4) NOT NULL, + `pay_price` bigint(20) NOT NULL, + `refund_price` bigint(20) NOT NULL, + `reason` varchar(256) NOT NULL, + `user_ip` varchar(50) NULL DEFAULT NULL, + `channel_order_no` varchar(64) NOT NULL, + `channel_refund_no` varchar(64) NULL DEFAULT NULL, + `success_time` datetime(0) NULL DEFAULT NULL, + `channel_error_code` varchar(128) NULL DEFAULT NULL, + `channel_error_msg` varchar(256) NULL DEFAULT NULL, + `channel_notify_data` varchar(1024) NULL, + `creator` varchar(64) NULL DEFAULT '', + `create_time` datetime(0) NOT NULL DEFAULT CURRENT_TIMESTAMP, + `updater` varchar(64) NULL DEFAULT '', + `update_time` datetime(0) NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + `deleted` bit(1) NOT NULL DEFAULT FALSE, + PRIMARY KEY ("id") +) COMMENT = '退款订单'; + +CREATE TABLE IF NOT EXISTS `pay_notify_task` ( + "id" number NOT NULL GENERATED BY DEFAULT AS IDENTITY, + `app_id` bigint(20) NOT NULL, + `type` tinyint(4) NOT NULL, + `data_id` bigint(20) NOT NULL, + `merchant_order_id` varchar(64) NOT NULL, + `status` tinyint(4) NOT NULL, + `next_notify_time` datetime(0) NULL DEFAULT NULL, + `last_execute_time` datetime(0) NULL DEFAULT NULL, + `notify_times` int NOT NULL, + `max_notify_times` int NOT NULL, + `notify_url` varchar(1024) NOT NULL, + `creator` varchar(64) NULL DEFAULT '', + `create_time` datetime(0) NOT NULL DEFAULT CURRENT_TIMESTAMP, + `updater` varchar(64) NULL DEFAULT '', + `update_time` datetime(0) NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + `deleted` bit(1) NOT NULL DEFAULT FALSE, + `tenant_id` bigint(20) NOT NULL DEFAULT 0, + PRIMARY KEY ("id") +) COMMENT = '支付通知任务'; + +CREATE TABLE IF NOT EXISTS `pay_notify_log` ( + "id" number NOT NULL GENERATED BY DEFAULT AS IDENTITY, + `task_id` bigint(20) NOT NULL, + `notify_times` int NOT NULL, + `response` varchar(1024) NOT NULL, + `status` tinyint(4) NOT NULL, + `creator` varchar(64) NULL DEFAULT '', + `create_time` datetime(0) NOT NULL DEFAULT CURRENT_TIMESTAMP, + `updater` varchar(64) NULL DEFAULT '', + `update_time` datetime(0) NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + `deleted` bit(1) NOT NULL DEFAULT FALSE, + PRIMARY KEY ("id") +) COMMENT = '支付通知日志'; diff --git a/yudao-module-pay/yudao-module-pay-api/pom.xml b/yudao-module-pay/yudao-module-pay-api/pom.xml deleted file mode 100644 index 0903abe..0000000 --- a/yudao-module-pay/yudao-module-pay-api/pom.xml +++ /dev/null @@ -1,33 +0,0 @@ - - - - yudao-module-pay - cn.iocoder.boot - ${revision} - - 4.0.0 - yudao-module-pay-api - jar - - ${project.artifactId} - - pay 模块 API,暴露给其它模块调用 - - - - - cn.iocoder.boot - yudao-common - - - - - org.springframework.boot - spring-boot-starter-validation - true - - - - diff --git a/yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/api/notify/dto/PayOrderNotifyReqDTO.java b/yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/api/notify/dto/PayOrderNotifyReqDTO.java deleted file mode 100644 index 8c01e8b..0000000 --- a/yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/api/notify/dto/PayOrderNotifyReqDTO.java +++ /dev/null @@ -1,34 +0,0 @@ -package cn.iocoder.yudao.module.pay.api.notify.dto; - -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Data; -import lombok.NoArgsConstructor; - -import jakarta.validation.constraints.NotEmpty; -import jakarta.validation.constraints.NotNull; - -/** - * 支付单的通知 Request DTO - * - * @author 芋道源码 - */ -@Data -@Builder -@NoArgsConstructor -@AllArgsConstructor -public class PayOrderNotifyReqDTO { - - /** - * 商户订单编号 - */ - @NotEmpty(message = "商户订单号不能为空") - private String merchantOrderId; - - /** - * 支付订单编号 - */ - @NotNull(message = "支付订单编号不能为空") - private Long payOrderId; - -} diff --git a/yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/api/notify/dto/PayRefundNotifyReqDTO.java b/yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/api/notify/dto/PayRefundNotifyReqDTO.java deleted file mode 100644 index 6746c31..0000000 --- a/yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/api/notify/dto/PayRefundNotifyReqDTO.java +++ /dev/null @@ -1,40 +0,0 @@ -package cn.iocoder.yudao.module.pay.api.notify.dto; - -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Data; -import lombok.NoArgsConstructor; - -import jakarta.validation.constraints.NotEmpty; -import jakarta.validation.constraints.NotNull; - -/** - * 退款单的通知 Request DTO - * - * @author 芋道源码 - */ -@Data -@Builder -@NoArgsConstructor -@AllArgsConstructor -public class PayRefundNotifyReqDTO { - - /** - * 商户退款单编号 - */ - @NotEmpty(message = "商户退款单编号不能为空") - private String merchantOrderId; - - /** - * 商户退款编号 - */ - @NotEmpty(message = "商户退款编号不能为空") - private String merchantRefundId; - - /** - * 支付退款编号 - */ - @NotNull(message = "支付退款编号不能为空") - private Long payRefundId; - -} diff --git a/yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/api/notify/dto/PayTransferNotifyReqDTO.java b/yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/api/notify/dto/PayTransferNotifyReqDTO.java deleted file mode 100644 index f3a92d8..0000000 --- a/yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/api/notify/dto/PayTransferNotifyReqDTO.java +++ /dev/null @@ -1,34 +0,0 @@ -package cn.iocoder.yudao.module.pay.api.notify.dto; - -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Data; - -import jakarta.validation.constraints.NotEmpty; -import jakarta.validation.constraints.NotNull; -import lombok.NoArgsConstructor; - -/** - * 转账单的通知 Request DTO - * - * @author jason - */ -@Data -@Builder -@NoArgsConstructor -@AllArgsConstructor -public class PayTransferNotifyReqDTO { - - /** - * 商户转账单号 - */ - @NotEmpty(message = "商户转账单号不能为空") - private String merchantTransferId; - - /** - * 转账订单编号 - */ - @NotNull(message = "转账订单编号不能为空") - private Long payTransferId; - -} diff --git a/yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/api/notify/package-info.java b/yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/api/notify/package-info.java deleted file mode 100644 index 60c4ca8..0000000 --- a/yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/api/notify/package-info.java +++ /dev/null @@ -1,4 +0,0 @@ -/** - * 占位符,无特殊作用 - */ -package cn.iocoder.yudao.module.pay.api.notify; diff --git a/yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/api/order/PayOrderApi.java b/yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/api/order/PayOrderApi.java deleted file mode 100644 index 965d79a..0000000 --- a/yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/api/order/PayOrderApi.java +++ /dev/null @@ -1,40 +0,0 @@ -package cn.iocoder.yudao.module.pay.api.order; - -import cn.iocoder.yudao.module.pay.api.order.dto.PayOrderCreateReqDTO; -import cn.iocoder.yudao.module.pay.api.order.dto.PayOrderRespDTO; - -import jakarta.validation.Valid; - -/** - * 支付单 API 接口 - * - * @author LeeYan9 - * @since 2022-08-26 - */ -public interface PayOrderApi { - - /** - * 创建支付单 - * - * @param reqDTO 创建请求 - * @return 支付单编号 - */ - Long createOrder(@Valid PayOrderCreateReqDTO reqDTO); - - /** - * 获得支付单 - * - * @param id 支付单编号 - * @return 支付单 - */ - PayOrderRespDTO getOrder(Long id); - - /** - * 更新支付订单价格 - * - * @param id 支付单编号 - * @param payPrice 支付单价格 - */ - void updatePayOrderPrice(Long id, Integer payPrice); - -} diff --git a/yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/api/order/dto/PayOrderCreateReqDTO.java b/yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/api/order/dto/PayOrderCreateReqDTO.java deleted file mode 100644 index 3a7b181..0000000 --- a/yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/api/order/dto/PayOrderCreateReqDTO.java +++ /dev/null @@ -1,65 +0,0 @@ -package cn.iocoder.yudao.module.pay.api.order.dto; - -import jakarta.validation.constraints.DecimalMin; -import jakarta.validation.constraints.NotEmpty; -import jakarta.validation.constraints.NotNull; -import lombok.Data; -import org.hibernate.validator.constraints.Length; - -import java.io.Serializable; -import java.time.LocalDateTime; - -/** - * 支付单创建 Request DTO - */ -@Data -public class PayOrderCreateReqDTO implements Serializable { - - public static final int SUBJECT_MAX_LENGTH = 32; - - /** - * 应用标识 - */ - @NotNull(message = "应用标识不能为空") - private String appKey; - /** - * 用户 IP - */ - @NotEmpty(message = "用户 IP 不能为空") - private String userIp; - - // ========== 商户相关字段 ========== - - /** - * 商户订单编号 - */ - @NotEmpty(message = "商户订单编号不能为空") - private String merchantOrderId; - /** - * 商品标题 - */ - @NotEmpty(message = "商品标题不能为空") - @Length(max = SUBJECT_MAX_LENGTH, message = "商品标题不能超过 32") - private String subject; - /** - * 商品描述 - */ - @Length(max = 128, message = "商品描述信息长度不能超过128") - private String body; - - // ========== 订单相关字段 ========== - - /** - * 支付金额,单位:分 - */ - @NotNull(message = "支付金额不能为空") - @DecimalMin(value = "0", inclusive = false, message = "支付金额必须大于零") - private Integer price; - - /** - * 支付过期时间 - */ - @NotNull(message = "支付过期时间不能为空") - private LocalDateTime expireTime; - -} diff --git a/yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/api/order/dto/PayOrderRespDTO.java b/yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/api/order/dto/PayOrderRespDTO.java deleted file mode 100644 index 1287ce7..0000000 --- a/yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/api/order/dto/PayOrderRespDTO.java +++ /dev/null @@ -1,68 +0,0 @@ -package cn.iocoder.yudao.module.pay.api.order.dto; - -import cn.iocoder.yudao.module.pay.enums.order.PayOrderStatusEnum; -import lombok.Data; - -import java.time.LocalDateTime; - -/** - * 支付单信息 Response DTO - * - * @author 芋道源码 - */ -@Data -public class PayOrderRespDTO { - - /** - * 订单编号,数据库自增 - */ - private Long id; - /** - * 渠道编码 - * - * 枚举 PayChannelEnum - */ - private String channelCode; - - // ========== 商户相关字段 ========== - /** - * 商户订单编号 - * 例如说,内部系统 A 的订单号。需要保证每个 PayMerchantDO 唯一 - */ - private String merchantOrderId; - - // ========== 订单相关字段 ========== - /** - * 商品标题 - */ - private String subject; - /** - * 支付金额,单位:分 - */ - private Integer price; - /** - * 支付状态 - * - * 枚举 {@link PayOrderStatusEnum} - */ - private Integer status; - - /** - * 订单支付成功时间 - */ - private LocalDateTime successTime; - - // ========== 渠道相关字段 ========== - - /** - * 渠道用户编号 - * - * 例如说,微信 openid、支付宝账号 - */ - private String channelUserId; - /** - * 渠道订单号 - */ - private String channelOrderNo; - -} diff --git a/yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/api/refund/PayRefundApi.java b/yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/api/refund/PayRefundApi.java deleted file mode 100644 index fa768f8..0000000 --- a/yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/api/refund/PayRefundApi.java +++ /dev/null @@ -1,31 +0,0 @@ -package cn.iocoder.yudao.module.pay.api.refund; - -import cn.iocoder.yudao.module.pay.api.refund.dto.PayRefundCreateReqDTO; -import cn.iocoder.yudao.module.pay.api.refund.dto.PayRefundRespDTO; - -import jakarta.validation.Valid; - -/** - * 退款单 API 接口 - * - * @author 芋道源码 - */ -public interface PayRefundApi { - - /** - * 创建退款单 - * - * @param reqDTO 创建请求 - * @return 退款单编号 - */ - Long createRefund(@Valid PayRefundCreateReqDTO reqDTO); - - /** - * 获得退款单 - * - * @param id 退款单编号 - * @return 退款单 - */ - PayRefundRespDTO getRefund(Long id); - -} diff --git a/yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/api/refund/dto/PayRefundCreateReqDTO.java b/yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/api/refund/dto/PayRefundCreateReqDTO.java deleted file mode 100644 index 6910fc2..0000000 --- a/yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/api/refund/dto/PayRefundCreateReqDTO.java +++ /dev/null @@ -1,57 +0,0 @@ -package cn.iocoder.yudao.module.pay.api.refund.dto; - -import jakarta.validation.constraints.Min; -import jakarta.validation.constraints.NotEmpty; -import jakarta.validation.constraints.NotNull; -import lombok.Data; -import org.hibernate.validator.constraints.Length; - -/** - * 退款单创建 Request DTO - * - * @author 芋道源码 - */ -@Data -public class PayRefundCreateReqDTO { - - /** - * 应用标识 - */ - @NotNull(message = "应用标识不能为空") - private String appKey; - /** - * 用户 IP - */ - @NotEmpty(message = "用户 IP 不能为空") - private String userIp; - - // ========== 商户相关字段 ========== - /** - * 商户订单编号 - */ - @NotEmpty(message = "商户订单编号不能为空") - private String merchantOrderId; - - /** - * 商户退款编号 - */ - @NotEmpty(message = "商户退款编号不能为空") - private String merchantRefundId; - - /** - * 退款描述 - */ - @NotEmpty(message = "退款描述不能为空") - @Length(max = 128, message = "退款描述长度不能超过 128") - private String reason; - - // ========== 订单相关字段 ========== - - /** - * 退款金额,单位:分 - */ - @NotNull(message = "退款金额不能为空") - @Min(value = 1, message = "退款金额必须大于零") - private Integer price; - -} diff --git a/yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/api/refund/dto/PayRefundRespDTO.java b/yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/api/refund/dto/PayRefundRespDTO.java deleted file mode 100644 index 725f391..0000000 --- a/yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/api/refund/dto/PayRefundRespDTO.java +++ /dev/null @@ -1,65 +0,0 @@ -package cn.iocoder.yudao.module.pay.api.refund.dto; - -import cn.iocoder.yudao.module.pay.enums.refund.PayRefundStatusEnum; -import lombok.Data; - -import java.time.LocalDateTime; - -/** - * 退款单信息 Response DTO - * - * @author 芋道源码 - */ -@Data -public class PayRefundRespDTO { - - /** - * 退款单编号 - */ - private Long id; - - /** - * 渠道编码 - * - * 枚举 PayChannelEnum - */ - private String channelCode; - - // ========== 退款相关字段 ========== - /** - * 退款状态 - * - * 枚举 {@link PayRefundStatusEnum} - */ - private Integer status; - /** - * 退款金额,单位:分 - */ - private Integer refundPrice; - - // ========== 商户相关字段 ========== - /** - * 商户订单编号 - */ - private String merchantOrderId; - /** - * 商户退款编号 - */ - private String merchantRefundId; - /** - * 退款成功时间 - */ - private LocalDateTime successTime; - - // ========== 渠道相关字段 ========== - - /** - * 调用渠道的错误码 - */ - private String channelErrorCode; - /** - * 调用渠道的错误提示 - */ - private String channelErrorMsg; - -} diff --git a/yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/api/transfer/PayTransferApi.java b/yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/api/transfer/PayTransferApi.java deleted file mode 100644 index e3227eb..0000000 --- a/yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/api/transfer/PayTransferApi.java +++ /dev/null @@ -1,31 +0,0 @@ -package cn.iocoder.yudao.module.pay.api.transfer; - -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; -import jakarta.validation.Valid; - -/** - * 转账单 API 接口 - * - * @author jason - */ -public interface PayTransferApi { - - /** - * 创建转账单 - * - * @param reqDTO 创建请求 - * @return 创建结果 - */ - PayTransferCreateRespDTO createTransfer(@Valid PayTransferCreateReqDTO reqDTO); - - /** - * 获得转账单 - * - * @param id 转账单编号 - * @return 转账单 - */ - PayTransferRespDTO getTransfer(Long id); - -} diff --git a/yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/api/transfer/dto/PayTransferCreateReqDTO.java b/yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/api/transfer/dto/PayTransferCreateReqDTO.java deleted file mode 100644 index 1ac5c04..0000000 --- a/yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/api/transfer/dto/PayTransferCreateReqDTO.java +++ /dev/null @@ -1,120 +0,0 @@ -package cn.iocoder.yudao.module.pay.api.transfer.dto; - -import cn.hutool.core.util.StrUtil; -import cn.iocoder.yudao.framework.common.util.json.JsonUtils; -import jakarta.validation.constraints.Min; -import jakarta.validation.constraints.NotEmpty; -import jakarta.validation.constraints.NotNull; -import lombok.Data; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -/** - * 转账单创建 Request DTO - * - * @author jason - */ -@Data -public class PayTransferCreateReqDTO { - - /** - * 应用标识 - */ - @NotNull(message = "应用标识不能为空") - private String appKey; - - /** - * 转账渠道 - */ - @NotEmpty(message = "转账渠道不能为空") - private String channelCode; - - /** - * 转账渠道的额外参数 - */ - private Map channelExtras; - - /** - * 用户 IP - */ - @NotEmpty(message = "用户 IP 不能为空") - private String userIp; - - /** - * 商户转账单编号 - */ - @NotEmpty(message = "商户转账单编号能为空") - private String merchantTransferId; - - /** - * 转账金额,单位:分 - */ - @Min(value = 1, message = "转账金额必须大于零") - @NotNull(message = "转账金额不能为空") - private Integer price; - - /** - * 转账标题 - */ - @NotEmpty(message = "转账标题不能为空") - private String subject; - - /** - * 收款人账号 - * - * 微信场景下:openid - * 支付宝场景下:支付宝账号 - */ - @NotEmpty(message = "收款人账号不能为空") - private String userAccount; - /** - * 收款人姓名 - */ - private String userName; - - /** - * 【微信】现金营销场景 - * - * @param activityName 活动名称 - * @param rewardDescription 奖励说明 - * @return channelExtras - */ - public static Map buildWeiXinChannelExtra1000(String activityName, String rewardDescription) { - return buildWeiXinChannelExtra(1000, - "活动名称", activityName, - "奖励说明", rewardDescription); - } - - /** - * 【微信】企业报销场景 - * - * @param expenseType 报销类型 - * @param expenseDescription 报销说明 - * @return channelExtras - */ - public static Map buildWeiXinChannelExtra1006(String expenseType, String expenseDescription) { - return buildWeiXinChannelExtra(1006, - "报销类型", expenseType, - "报销说明", expenseDescription); - } - - private static Map buildWeiXinChannelExtra(Integer sceneId, String... values) { - Map channelExtras = new HashMap<>(); - // 构建场景报备信息列表 - List> sceneReportInfos = new ArrayList<>(); - for (int i = 0; i < values.length; i += 2) { - Map info = new HashMap<>(); - info.put("infoType", values[i]); - info.put("infoContent", values[i + 1]); - sceneReportInfos.add(info); - } - // 设置场景ID和场景报备信息 - channelExtras.put("sceneId", StrUtil.toString(sceneId)); - channelExtras.put("sceneReportInfos", JsonUtils.toJsonString(sceneReportInfos)); - return channelExtras; - } - -} diff --git a/yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/api/transfer/dto/PayTransferCreateRespDTO.java b/yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/api/transfer/dto/PayTransferCreateRespDTO.java deleted file mode 100644 index a445eb0..0000000 --- a/yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/api/transfer/dto/PayTransferCreateRespDTO.java +++ /dev/null @@ -1,28 +0,0 @@ -package cn.iocoder.yudao.module.pay.api.transfer.dto; - -import lombok.Data; - -/** - * 转账单创建 Response DTO - * - * @author 芋道源码 - */ -@Data -public class PayTransferCreateRespDTO { - - /** - * 编号 - */ - private Long id; - - // ========== 其它字段 ========== - - /** - * 渠道 package 信息 - * - * 特殊:目前只有微信转账有这个东西!!! - * @see JSAPI 调起用户确认收款 - */ - private String channelPackageInfo; - -} diff --git a/yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/api/transfer/dto/PayTransferRespDTO.java b/yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/api/transfer/dto/PayTransferRespDTO.java deleted file mode 100644 index 52126ce..0000000 --- a/yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/api/transfer/dto/PayTransferRespDTO.java +++ /dev/null @@ -1,81 +0,0 @@ -package cn.iocoder.yudao.module.pay.api.transfer.dto; - -import cn.iocoder.yudao.module.pay.enums.PayChannelEnum; -import cn.iocoder.yudao.module.pay.enums.transfer.PayTransferStatusEnum; -import lombok.Data; - -import java.time.LocalDateTime; - -@Data -public class PayTransferRespDTO { - - /** - * 编号 - */ - private Long id; - - /** - * 转账单号 - */ - private String no; - - /** - * 转账渠道编码 - * - * 枚举 {@link PayChannelEnum} - */ - private String channelCode; - - // ========== 商户相关字段 ========== - - /** - * 商户转账单编号 - */ - private String merchantTransferId; - - // ========== 转账相关字段 ========== - - /** - * 转账金额,单位:分 - */ - private Integer price; - - /** - * 转账状态 - * - * 枚举 {@link PayTransferStatusEnum} - */ - private Integer status; - - /** - * 订单转账成功时间 - */ - private LocalDateTime successTime; - - // ========== 其它字段 ========== - - /** - * 调用渠道的错误码 - */ - private String channelErrorCode; - /** - * 调用渠道的错误提示 - */ - private String channelErrorMsg; - - /** - * 渠道 package 信息 - * - * 特殊:目前只有微信转账有这个东西!!! - * @see JSAPI 调起用户确认收款 - */ - private String channelPackageInfo; - /** - * 渠道商户号 - * - * 特殊:目前只有微信转账有这个东西!!! - * @see JSAPI 调起用户确认收款 - */ - private String channelMchId; - -} diff --git a/yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/api/wallet/PayWalletApi.java b/yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/api/wallet/PayWalletApi.java deleted file mode 100644 index 5586929..0000000 --- a/yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/api/wallet/PayWalletApi.java +++ /dev/null @@ -1,29 +0,0 @@ -package cn.iocoder.yudao.module.pay.api.wallet; - -import cn.iocoder.yudao.module.pay.api.wallet.dto.PayWalletAddBalanceReqDTO; -import cn.iocoder.yudao.module.pay.api.wallet.dto.PayWalletRespDTO; - -/** - * 钱包 API 接口 - * - * @author liurulin - */ -public interface PayWalletApi { - - /** - * 添加钱包余额 - * - * @param reqDTO 增加余额请求 - */ - void addWalletBalance(PayWalletAddBalanceReqDTO reqDTO); - - /** - * 获取钱包信息 - * - * @param userId 用户编号 - * @param userType 用户类型 - * @return 钱包信息 - */ - PayWalletRespDTO getOrCreateWallet(Long userId, Integer userType); - -} diff --git a/yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/api/wallet/dto/PayWalletAddBalanceReqDTO.java b/yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/api/wallet/dto/PayWalletAddBalanceReqDTO.java deleted file mode 100644 index 264649d..0000000 --- a/yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/api/wallet/dto/PayWalletAddBalanceReqDTO.java +++ /dev/null @@ -1,49 +0,0 @@ -package cn.iocoder.yudao.module.pay.api.wallet.dto; - -import cn.iocoder.yudao.framework.common.enums.UserTypeEnum; -import jakarta.validation.constraints.NotNull; -import lombok.Data; - -/** - * 钱包余额增加 Request DTO - * - * @author 芋道源码 - */ -@Data -public class PayWalletAddBalanceReqDTO { - - /** - * 用户编号 - * - * 关联 MemberUserDO 的 id 属性,或者 AdminUserDO 的 id 属性 - */ - @NotNull(message = "用户编号不能为空") - private Long userId; - /** - * 用户类型 - * - * 关联 {@link UserTypeEnum} - */ - @NotNull(message = "用户类型不能为空") - private Integer userType; - - /** - * 关联业务分类 - */ - @NotNull(message = "关联业务分类不能为空") - private Integer bizType; - /** - * 关联业务编号 - */ - @NotNull(message = "关联业务编号不能为空") - private String bizId; - - /** - * 交易金额,单位分 - * - * 正值表示余额增加,负值表示余额减少 - */ - @NotNull(message = "交易金额不能为空") - private Integer price; - -} diff --git a/yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/api/wallet/dto/PayWalletRespDTO.java b/yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/api/wallet/dto/PayWalletRespDTO.java deleted file mode 100644 index 9a8db79..0000000 --- a/yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/api/wallet/dto/PayWalletRespDTO.java +++ /dev/null @@ -1,52 +0,0 @@ -package cn.iocoder.yudao.module.pay.api.wallet.dto; - -import cn.iocoder.yudao.framework.common.enums.UserTypeEnum; -import lombok.Data; - -/** - * 钱包 Response DTO - * - * @author jason - */ -@Data -public class PayWalletRespDTO { - - /** - * 编号 - */ - private Long id; - - /** - * 用户 id - * - * 关联 MemberUserDO 的 id 编号 - * 关联 AdminUserDO 的 id 编号 - */ - private Long userId; - /** - * 用户类型, 预留 多商户转帐可能需要用到 - * - * 关联 {@link UserTypeEnum} - */ - private Integer userType; - - /** - * 余额,单位分 - */ - private Integer balance; - - /** - * 冻结金额,单位分 - */ - private Integer freezePrice; - - /** - * 累计支出,单位分 - */ - private Integer totalExpense; - /** - * 累计充值,单位分 - */ - private Integer totalRecharge; - -} diff --git a/yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/enums/DictTypeConstants.java b/yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/enums/DictTypeConstants.java deleted file mode 100644 index 680ed9b..0000000 --- a/yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/enums/DictTypeConstants.java +++ /dev/null @@ -1,20 +0,0 @@ -package cn.iocoder.yudao.module.pay.enums; - -/** - * Pay 字典类型的枚举类 - * - * @author 芋道源码 - */ -public interface DictTypeConstants { - - String CHANNEL_CODE = "pay_channel_code"; // 支付渠道编码 - - String ORDER_STATUS = "pay_order_status"; // 支付渠道 - - String REFUND_STATUS = "pay_order_status"; // 退款状态 - - String NOTIFY_STATUS = "pay_notify_status"; // 回调状态 - - String TRANSFER_STATUS = "pay_transfer_status"; // 转账状态 - -} diff --git a/yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/enums/ErrorCodeConstants.java b/yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/enums/ErrorCodeConstants.java deleted file mode 100644 index 660cc23..0000000 --- a/yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/enums/ErrorCodeConstants.java +++ /dev/null @@ -1,97 +0,0 @@ -package cn.iocoder.yudao.module.pay.enums; - -import cn.iocoder.yudao.framework.common.exception.ErrorCode; - -/** - * Pay 错误码 Core 枚举类 - * - * pay 系统,使用 1-007-000-000 段 - */ -public interface ErrorCodeConstants { - - // ========== APP 模块 1-007-000-000 ========== - ErrorCode APP_NOT_FOUND = new ErrorCode(1_007_000_000, "App 不存在"); - ErrorCode APP_IS_DISABLE = new ErrorCode(1_007_000_002, "App 已经被禁用"); - ErrorCode APP_EXIST_ORDER_CANT_DELETE = new ErrorCode(1_007_000_003, "支付应用存在支付订单,无法删除"); - ErrorCode APP_EXIST_REFUND_CANT_DELETE = new ErrorCode(1_007_000_004, "支付应用存在退款订单,无法删除"); - ErrorCode APP_KEY_EXISTS = new ErrorCode(1_007_000_005, "支付应用标识已经存在"); - - // ========== CHANNEL 模块 1-007-001-000 ========== - ErrorCode CHANNEL_NOT_FOUND = new ErrorCode(1_007_001_000, "支付渠道的配置不存在"); - ErrorCode CHANNEL_IS_DISABLE = new ErrorCode(1_007_001_001, "支付渠道已经禁用"); - ErrorCode CHANNEL_EXIST_SAME_CHANNEL_ERROR = new ErrorCode(1_007_001_004, "已存在相同的渠道"); - - // ========== ORDER 模块 1-007-002-000 ========== - ErrorCode PAY_ORDER_NOT_FOUND = new ErrorCode(1_007_002_000, "支付订单不存在"); - ErrorCode PAY_ORDER_STATUS_IS_NOT_WAITING = new ErrorCode(1_007_002_001, "支付订单不处于待支付"); - ErrorCode PAY_ORDER_STATUS_IS_SUCCESS = new ErrorCode(1_007_002_002, "订单已支付,请刷新页面"); - ErrorCode PAY_ORDER_IS_EXPIRED = new ErrorCode(1_007_002_003, "支付订单已经过期"); - ErrorCode PAY_ORDER_SUBMIT_CHANNEL_ERROR = new ErrorCode(1_007_002_004, "发起支付报错,错误码:{},错误提示:{}"); - ErrorCode PAY_ORDER_REFUND_FAIL_STATUS_ERROR = new ErrorCode(1_007_002_005, "支付订单退款失败,原因:状态不是已支付或已退款"); - - // ========== ORDER 模块(拓展单) 1-007-003-000 ========== - ErrorCode PAY_ORDER_EXTENSION_NOT_FOUND = new ErrorCode(1_007_003_000, "支付交易拓展单不存在"); - ErrorCode PAY_ORDER_EXTENSION_STATUS_IS_NOT_WAITING = new ErrorCode(1_007_003_001, "支付交易拓展单不处于待支付"); - ErrorCode PAY_ORDER_EXTENSION_IS_PAID = new ErrorCode(1_007_003_002, "订单已支付,请等待支付结果"); - - // ========== 支付模块(退款) 1-007-006-000 ========== - ErrorCode REFUND_PRICE_EXCEED = new ErrorCode(1_007_006_000, "退款金额超过订单可退款金额"); - ErrorCode REFUND_HAS_REFUNDING = new ErrorCode(1_007_006_002, "已经有退款在处理中"); - ErrorCode REFUND_EXISTS = new ErrorCode(1_007_006_003, "已经存在退款单"); - ErrorCode REFUND_NOT_FOUND = new ErrorCode(1_007_006_004, "支付退款单不存在"); - ErrorCode REFUND_STATUS_IS_NOT_WAITING = new ErrorCode(1_007_006_005, "支付退款单不处于待退款"); - - // ========== 钱包模块 1-007-007-000 ========== - ErrorCode WALLET_NOT_FOUND = new ErrorCode(1_007_007_000, "用户钱包不存在"); - ErrorCode WALLET_BALANCE_NOT_ENOUGH = new ErrorCode(1_007_007_001, "钱包余额不足"); - ErrorCode WALLET_TRANSACTION_NOT_FOUND = new ErrorCode(1_007_007_002, "未找到对应的钱包交易"); - ErrorCode WALLET_REFUND_EXIST = new ErrorCode(1_007_007_003, "已经存在钱包退款"); - ErrorCode WALLET_FREEZE_PRICE_NOT_ENOUGH = new ErrorCode(1_007_007_004, "钱包冻结余额不足"); - - // ========== 钱包充值模块 1-007-008-000 ========== - ErrorCode WALLET_RECHARGE_NOT_FOUND = new ErrorCode(1_007_008_000, "钱包充值记录不存在"); - ErrorCode WALLET_RECHARGE_UPDATE_PAID_STATUS_NOT_UNPAID = new ErrorCode(1_007_008_001, "钱包充值更新支付状态失败,钱包充值记录不是【未支付】状态"); - ErrorCode WALLET_RECHARGE_UPDATE_PAID_PAY_ORDER_ID_ERROR = new ErrorCode(1_007_008_002, "钱包充值更新支付状态失败,支付单编号不匹配"); - ErrorCode WALLET_RECHARGE_UPDATE_PAID_PAY_ORDER_STATUS_NOT_SUCCESS = new ErrorCode(1_007_008_003, "钱包充值更新支付状态失败,支付单状态不是【支付成功】状态"); - ErrorCode WALLET_RECHARGE_UPDATE_PAID_PAY_PRICE_NOT_MATCH = new ErrorCode(1_007_008_004, "钱包充值更新支付状态失败,支付单金额不匹配"); - ErrorCode WALLET_RECHARGE_REFUND_FAIL_NOT_PAID = new ErrorCode(1_007_008_005, "钱包发起退款失败,钱包充值订单未支付"); - ErrorCode WALLET_RECHARGE_REFUND_FAIL_REFUNDED = new ErrorCode(1_007_008_006, "钱包发起退款失败,钱包充值订单已退款"); - ErrorCode WALLET_RECHARGE_REFUND_BALANCE_NOT_ENOUGH = new ErrorCode(1_007_008_007, "钱包发起退款失败,钱包余额不足"); - ErrorCode WALLET_RECHARGE_REFUND_FAIL_REFUND_ORDER_ID_ERROR = new ErrorCode(1_007_008_008, "钱包退款更新失败,钱包退款单编号不匹配"); - ErrorCode WALLET_RECHARGE_REFUND_FAIL_REFUND_NOT_FOUND = new ErrorCode(1_007_008_009, "钱包退款更新失败,退款订单不存在"); - ErrorCode WALLET_RECHARGE_REFUND_FAIL_REFUND_PRICE_NOT_MATCH = new ErrorCode(1_007_008_010, "钱包退款更新失败,退款单金额不匹配"); - ErrorCode WALLET_RECHARGE_PACKAGE_NOT_FOUND = new ErrorCode(1_007_008_011, "钱包充值套餐不存在"); - ErrorCode WALLET_RECHARGE_PACKAGE_IS_DISABLE = new ErrorCode(1_007_008_012, "钱包充值套餐已禁用"); - ErrorCode WALLET_RECHARGE_PACKAGE_NAME_EXISTS = new ErrorCode(1_007_008_013, "钱包充值套餐名称已存在"); - - // ========== 转账模块 1-007-009-000 ========== - ErrorCode PAY_TRANSFER_NOT_FOUND = new ErrorCode(1_007_009_001, "转账单不存在"); - ErrorCode PAY_TRANSFER_CREATE_CHANNEL_NOT_MATCH = new ErrorCode(1_007_009_002, "转账发起失败,原因:两次相同转账请求的类型不匹配"); - ErrorCode PAY_TRANSFER_CREATE_PRICE_NOT_MATCH = new ErrorCode(1_007_009_003, "转账发起失败,原因:两次相同转账请求的金额不匹配"); - ErrorCode PAY_TRANSFER_CREATE_FAIL_STATUS_NOT_CLOSED = new ErrorCode(1_007_009_004, "转账发起失败,原因:已经存在相同的转账单,且状态不是已关闭"); - ErrorCode PAY_TRANSFER_NOTIFY_FAIL_STATUS_IS_NOT_WAITING = new ErrorCode(1_007_009_006, "通知转账结果失败,原因:转账单不处于待转账"); - ErrorCode PAY_TRANSFER_NOTIFY_FAIL_STATUS_NOT_WAITING_OR_PROCESSING = new ErrorCode(1_007_009_007, "通知转账结果失败,原因:转账单不处于待转账或转账中"); - - // ========== 示例订单 1-007-900-000 ========== - ErrorCode DEMO_ORDER_NOT_FOUND = new ErrorCode(1_007_900_000, "示例订单不存在"); - ErrorCode DEMO_ORDER_UPDATE_PAID_STATUS_NOT_UNPAID = new ErrorCode(1_007_900_001, "示例订单更新支付状态失败,订单不是【未支付】状态"); - ErrorCode DEMO_ORDER_UPDATE_PAID_FAIL_PAY_ORDER_ID_ERROR = new ErrorCode(1_007_900_002, "示例订单更新支付状态失败,支付单编号不匹配"); - ErrorCode DEMO_ORDER_UPDATE_PAID_FAIL_PAY_ORDER_STATUS_NOT_SUCCESS = new ErrorCode(1_007_900_003, "示例订单更新支付状态失败,支付单状态不是【支付成功】状态"); - ErrorCode DEMO_ORDER_UPDATE_PAID_FAIL_PAY_PRICE_NOT_MATCH = new ErrorCode(1_007_900_004, "示例订单更新支付状态失败,支付单金额不匹配"); - ErrorCode DEMO_ORDER_REFUND_FAIL_NOT_PAID = new ErrorCode(1_007_900_005, "发起退款失败,示例订单未支付"); - ErrorCode DEMO_ORDER_REFUND_FAIL_REFUNDED = new ErrorCode(1_007_900_006, "发起退款失败,示例订单已退款"); - ErrorCode DEMO_ORDER_REFUND_FAIL_REFUND_NOT_FOUND = new ErrorCode(1_007_900_007, "发起退款失败,退款订单不存在"); - ErrorCode DEMO_ORDER_REFUND_FAIL_REFUND_NOT_SUCCESS = new ErrorCode(1_007_900_008, "发起退款失败,退款订单未退款成功"); - ErrorCode DEMO_ORDER_REFUND_FAIL_REFUND_ORDER_ID_ERROR = new ErrorCode(1_007_900_009, "发起退款失败,退款单编号不匹配"); - ErrorCode DEMO_ORDER_REFUND_FAIL_REFUND_PRICE_NOT_MATCH = new ErrorCode(1_007_900_010, "发起退款失败,退款单金额不匹配"); - - // ========== 示例提现单 1-007-901-000 ========== - ErrorCode DEMO_WITHDRAW_NOT_FOUND = new ErrorCode(1_007_901_000, "示例提现单不存在"); - ErrorCode DEMO_WITHDRAW_UPDATE_STATUS_FAIL_PAY_TRANSFER_ID_ERROR = new ErrorCode(1_007_901_001, "更新示例提现单状态失败,支付转账单编号不匹配"); - ErrorCode DEMO_WITHDRAW_UPDATE_STATUS_FAIL_PAY_TRANSFER_STATUS_NOT_SUCCESS_OR_CLOSED = new ErrorCode(1_007_901_002, "更新示例提现单状态失败,支付转账单状态不是【转账成功】或【转账失败】状态"); - ErrorCode DEMO_WITHDRAW_UPDATE_STATUS_FAIL_PAY_PRICE_NOT_MATCH = new ErrorCode(1_007_901_003, "更新示例提现单状态失败,支付转账单金额不匹配"); - ErrorCode DEMO_WITHDRAW_UPDATE_STATUS_FAIL_PAY_MERCHANT_EXISTS = new ErrorCode(1_007_901_004, "更新示例提现单状态失败,支付转账单商户订单号不匹配"); - ErrorCode DEMO_WITHDRAW_UPDATE_STATUS_FAIL_PAY_CHANNEL_NOT_MATCH = new ErrorCode(1_007_901_005, "更新示例提现单状态失败,支付转账单渠道不匹配"); - ErrorCode DEMO_WITHDRAW_TRANSFER_FAIL_STATUS_NOT_WAITING_OR_CLOSED = new ErrorCode(1_007_901_008, "发起转账失败,原因:示例提现单状态不是【等待提现】或【提现关闭】"); - -} diff --git a/yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/enums/MessageTemplateConstants.java b/yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/enums/MessageTemplateConstants.java deleted file mode 100644 index 9497d77..0000000 --- a/yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/enums/MessageTemplateConstants.java +++ /dev/null @@ -1,14 +0,0 @@ -package cn.iocoder.yudao.module.pay.enums; - -/** - * 通知模板枚举类 - * - * @author HUIHUI - */ -public interface MessageTemplateConstants { - - // ======================= 小程序订阅消息 ======================= - - String WXA_WALLET_RECHARGER_PAID = "充值成功通知"; - -} 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 deleted file mode 100644 index 2733282..0000000 --- a/yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/enums/PayChannelEnum.java +++ /dev/null @@ -1,67 +0,0 @@ -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; - -import java.util.Arrays; - -/** - * 支付渠道的编码的枚举 - * - * @author 芋道源码 - */ -@Getter -@AllArgsConstructor -public enum PayChannelEnum implements ArrayValuable { - - WX_PUB("wx_pub", "微信 JSAPI 支付"), // 公众号网页 - WX_LITE("wx_lite", "微信小程序支付"), - WX_APP("wx_app", "微信 App 支付"), - WX_NATIVE("wx_native", "微信 Native 支付"), - WX_WAP("wx_wap", "微信 Wap 网站支付"), // H5 网页 - WX_BAR("wx_bar", "微信付款码支付"), - - ALIPAY_PC("alipay_pc", "支付宝 PC 网站支付"), - ALIPAY_WAP("alipay_wap", "支付宝 Wap 网站支付"), - ALIPAY_APP("alipay_app", "支付宝App 支付"), - ALIPAY_QR("alipay_qr", "支付宝扫码支付"), - ALIPAY_BAR("alipay_bar", "支付宝条码支付"), - - MOCK("mock", "模拟支付"), - - WALLET("wallet", "钱包支付"); - - public static final String[] ARRAYS = Arrays.stream(values()).map(PayChannelEnum::getCode).toArray(String[]::new); - - /** - * 编码 - * - * 参考 支付渠道属性值 - */ - private final String code; - /** - * 名字 - */ - private final String name; - - @Override - public String[] array() { - 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/demo/PayDemoWithdrawStatusEnum.java b/yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/enums/demo/PayDemoWithdrawStatusEnum.java deleted file mode 100644 index d65bd3b..0000000 --- a/yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/enums/demo/PayDemoWithdrawStatusEnum.java +++ /dev/null @@ -1,42 +0,0 @@ -package cn.iocoder.yudao.module.pay.enums.demo; - -import lombok.AllArgsConstructor; -import lombok.Getter; - -import java.util.Objects; - -/** - * 示例提现单的状态枚举 - * - * @author jason - */ -@Getter -@AllArgsConstructor -public enum PayDemoWithdrawStatusEnum { - - WAITING(0, "等待提现"), - 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 isWaiting(Integer status) { - return Objects.equals(status, WAITING.getStatus()); - } - -} diff --git a/yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/enums/demo/PayDemoWithdrawTypeEnum.java b/yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/enums/demo/PayDemoWithdrawTypeEnum.java deleted file mode 100644 index c2d9469..0000000 --- a/yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/enums/demo/PayDemoWithdrawTypeEnum.java +++ /dev/null @@ -1,39 +0,0 @@ -package cn.iocoder.yudao.module.pay.enums.demo; - -import cn.iocoder.yudao.framework.common.core.ArrayValuable; -import lombok.AllArgsConstructor; -import lombok.Getter; - -import java.util.Arrays; - -/** - * 示例提现单的类型枚举 - * - * @author owen - */ -@AllArgsConstructor -@Getter -public enum PayDemoWithdrawTypeEnum implements ArrayValuable { - - WECHAT(2, "微信"), - ALIPAY(1, "支付宝"), - WALLET(3, "钱包"), - ; - - public static final Integer[] ARRAYS = Arrays.stream(values()).map(PayDemoWithdrawTypeEnum::getType).toArray(Integer[]::new); - - /** - * 类型 - */ - private final Integer type; - /** - * 名字 - */ - private final String name; - - @Override - public Integer[] array() { - return ARRAYS; - } - -} diff --git a/yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/enums/notify/PayNotifyStatusEnum.java b/yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/enums/notify/PayNotifyStatusEnum.java deleted file mode 100644 index b735598..0000000 --- a/yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/enums/notify/PayNotifyStatusEnum.java +++ /dev/null @@ -1,32 +0,0 @@ -package cn.iocoder.yudao.module.pay.enums.notify; - -import lombok.AllArgsConstructor; -import lombok.Getter; - -/** - * 支付通知状态枚举 - * - * @author 芋道源码 - */ -@Getter -@AllArgsConstructor -public enum PayNotifyStatusEnum { - - WAITING(0, "等待通知"), - SUCCESS(10, "通知成功"), - FAILURE(20, "通知失败"), // 多次尝试,彻底失败 - REQUEST_SUCCESS(21, "请求成功,但是结果失败"), - REQUEST_FAILURE(22, "请求失败"), - - ; - - /** - * 状态 - */ - private final Integer status; - /** - * 名字 - */ - private final String name; - -} diff --git a/yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/enums/notify/PayNotifyTypeEnum.java b/yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/enums/notify/PayNotifyTypeEnum.java deleted file mode 100644 index 873e015..0000000 --- a/yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/enums/notify/PayNotifyTypeEnum.java +++ /dev/null @@ -1,29 +0,0 @@ -package cn.iocoder.yudao.module.pay.enums.notify; - -import lombok.AllArgsConstructor; -import lombok.Getter; - -/** - * 支付通知类型 - * - * @author 芋道源码 - */ -@Getter -@AllArgsConstructor -public enum PayNotifyTypeEnum { - - ORDER(1, "支付单"), - REFUND(2, "退款单"), - TRANSFER(3, "转账单") - ; - - /** - * 类型 - */ - private final Integer type; - /** - * 名字 - */ - private final String name; - -} 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 deleted file mode 100644 index 5b5be4b..0000000 --- a/yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/enums/order/PayOrderStatusEnum.java +++ /dev/null @@ -1,84 +0,0 @@ -package cn.iocoder.yudao.module.pay.enums.order; - -import cn.iocoder.yudao.framework.common.core.ArrayValuable; -import cn.iocoder.yudao.framework.common.util.object.ObjectUtils; -import lombok.AllArgsConstructor; -import lombok.Getter; - -import java.util.Objects; - -/** - * 支付订单的状态枚举 - * - * @author 芋道源码 - */ -@Getter -@AllArgsConstructor -public enum PayOrderStatusEnum implements ArrayValuable { - - WAITING(0, "未支付"), - SUCCESS(10, "支付成功"), - REFUND(20, "已退款"), - CLOSED(30, "支付关闭"), // 注意:全部退款后,还是 REFUND 状态 - ; - - private final Integer status; - private final String name; - - @Override - public Integer[] array() { - return new Integer[0]; - } - - /** - * 判断是否等待支付 - * - * @param status 状态 - * @return 是否等待支付 - */ - public static boolean isWaiting(Integer status) { - return Objects.equals(status, WAITING.getStatus()); - } - - /** - * 判断是否支付成功 - * - * @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 isSuccessOrRefund(Integer status) { - return ObjectUtils.equalsAny(status, - SUCCESS.getStatus(), REFUND.getStatus()); - } - - /** - * 判断是否支付关闭 - * - * @param status 状态 - * @return 是否支付关闭 - */ - public static boolean isClosed(Integer status) { - return Objects.equals(status, CLOSED.getStatus()); - } - -} diff --git a/yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/enums/refund/PayRefundStatusEnum.java b/yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/enums/refund/PayRefundStatusEnum.java deleted file mode 100644 index 4ae14cc..0000000 --- a/yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/enums/refund/PayRefundStatusEnum.java +++ /dev/null @@ -1,32 +0,0 @@ -package cn.iocoder.yudao.module.pay.enums.refund; - -import lombok.AllArgsConstructor; -import lombok.Getter; - -import java.util.Objects; - -/** - * 渠道的退款状态枚举 - * - * @author 芋道源码 - */ -@Getter -@AllArgsConstructor -public enum PayRefundStatusEnum { - - 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-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 deleted file mode 100644 index 7a345f7..0000000 --- a/yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/enums/transfer/PayTransferStatusEnum.java +++ /dev/null @@ -1,67 +0,0 @@ -package cn.iocoder.yudao.module.pay.enums.transfer; - -import lombok.AllArgsConstructor; -import lombok.Getter; - -import java.util.Objects; - -/** - * 渠道的转账状态枚举 - * - * @author jason - */ -@Getter -@AllArgsConstructor -public enum PayTransferStatusEnum { - - 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 isWaiting(Integer status) { - return Objects.equals(status, WAITING.getStatus()); - } - - public static boolean isProcessing(Integer status) { - return Objects.equals(status, PROCESSING.getStatus()); - } - - /** - * 是否处于待转账或者转账中的状态 - * - * @param status 状态 - * @return 是否 - */ - public static boolean isWaitingOrProcessing(Integer status) { - return isWaiting(status) || isProcessing(status); - } - - /** - * 是否处于成功或者关闭中的状态 - * - * @param status 状态 - * @return 是否 - */ - public static boolean isSuccessOrClosed(Integer status) { - return isSuccess(status) || isClosed(status); - } - -} diff --git a/yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/enums/wallet/PayWalletBizTypeEnum.java b/yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/enums/wallet/PayWalletBizTypeEnum.java deleted file mode 100644 index f2987cf..0000000 --- a/yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/enums/wallet/PayWalletBizTypeEnum.java +++ /dev/null @@ -1,45 +0,0 @@ -package cn.iocoder.yudao.module.pay.enums.wallet; - -import cn.iocoder.yudao.framework.common.core.ArrayValuable; -import lombok.AllArgsConstructor; -import lombok.Getter; - -import java.util.Arrays; - -/** - * 钱包交易业务分类 - * - * @author jason - */ -@AllArgsConstructor -@Getter -public enum PayWalletBizTypeEnum implements ArrayValuable { - - RECHARGE(1, "充值"), - RECHARGE_REFUND(2, "充值退款"), - PAYMENT(3, "支付"), - PAYMENT_REFUND(4, "支付退款"), - UPDATE_BALANCE(5, "更新余额"), - TRANSFER(6, "转账"); - - /** - * 业务分类 - */ - private final Integer type; - /** - * 说明 - */ - private final String description; - - public static final Integer[] ARRAYS = Arrays.stream(values()).map(PayWalletBizTypeEnum::getType).toArray(Integer[]::new); - - @Override - public Integer[] array() { - return ARRAYS; - } - - public static PayWalletBizTypeEnum valueOf(Integer type) { - return Arrays.stream(values()).filter(item -> item.getType().equals(type)).findFirst().orElse(null); - } - -} diff --git a/yudao-module-pay/yudao-module-pay-biz/pom.xml b/yudao-module-pay/yudao-module-pay-biz/pom.xml deleted file mode 100644 index 6089d53..0000000 --- a/yudao-module-pay/yudao-module-pay-biz/pom.xml +++ /dev/null @@ -1,93 +0,0 @@ - - - - cn.iocoder.boot - yudao-module-pay - ${revision} - - 4.0.0 - yudao-module-pay-biz - jar - - ${project.artifactId} - - pay 模块,我们放支付业务,提供业务的支付能力。 - 例如说:商户、应用、支付、退款等等 - - - - - cn.iocoder.boot - yudao-module-system-api - ${revision} - - - cn.iocoder.boot - yudao-module-pay-api - ${revision} - - - - - cn.iocoder.boot - yudao-spring-boot-starter-biz-tenant - - - - - cn.iocoder.boot - yudao-spring-boot-starter-security - - - - - cn.iocoder.boot - yudao-spring-boot-starter-mybatis - - - - cn.iocoder.boot - yudao-spring-boot-starter-redis - - - - - cn.iocoder.boot - yudao-spring-boot-starter-job - - - - - cn.iocoder.boot - yudao-spring-boot-starter-test - test - - - - - 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/order/PayOrderApiImpl.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/api/order/PayOrderApiImpl.java deleted file mode 100644 index 3584f47..0000000 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/api/order/PayOrderApiImpl.java +++ /dev/null @@ -1,39 +0,0 @@ -package cn.iocoder.yudao.module.pay.api.order; - -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.convert.order.PayOrderConvert; -import cn.iocoder.yudao.module.pay.dal.dataobject.order.PayOrderDO; -import cn.iocoder.yudao.module.pay.service.order.PayOrderService; -import org.springframework.stereotype.Service; - -import jakarta.annotation.Resource; - -/** - * 支付单 API 实现类 - * - * @author 芋道源码 - */ -@Service -public class PayOrderApiImpl implements PayOrderApi { - - @Resource - private PayOrderService payOrderService; - - @Override - public Long createOrder(PayOrderCreateReqDTO reqDTO) { - return payOrderService.createOrder(reqDTO); - } - - @Override - public PayOrderRespDTO getOrder(Long id) { - PayOrderDO order = payOrderService.getOrder(id); - return PayOrderConvert.INSTANCE.convert2(order); - } - - @Override - public void updatePayOrderPrice(Long id, Integer payPrice) { - payOrderService.updatePayOrderPrice(id, payPrice); - } - -} diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/api/refund/PayRefundApiImpl.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/api/refund/PayRefundApiImpl.java deleted file mode 100644 index ccc538b..0000000 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/api/refund/PayRefundApiImpl.java +++ /dev/null @@ -1,35 +0,0 @@ -package cn.iocoder.yudao.module.pay.api.refund; - -import cn.iocoder.yudao.framework.common.util.object.BeanUtils; -import cn.iocoder.yudao.module.pay.api.refund.dto.PayRefundCreateReqDTO; -import cn.iocoder.yudao.module.pay.api.refund.dto.PayRefundRespDTO; -import cn.iocoder.yudao.module.pay.dal.dataobject.refund.PayRefundDO; -import cn.iocoder.yudao.module.pay.service.refund.PayRefundService; -import jakarta.annotation.Resource; -import org.springframework.stereotype.Service; -import org.springframework.validation.annotation.Validated; - -/** - * 退款单 API 实现类 - * - * @author 芋道源码 - */ -@Service -@Validated -public class PayRefundApiImpl implements PayRefundApi { - - @Resource - private PayRefundService payRefundService; - - @Override - public Long createRefund(PayRefundCreateReqDTO reqDTO) { - return payRefundService.createRefund(reqDTO); - } - - @Override - public PayRefundRespDTO getRefund(Long id) { - PayRefundDO refund = payRefundService.getRefund(id); - return BeanUtils.toBean(refund, PayRefundRespDTO.class); - } - -} 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 deleted file mode 100644 index 6c6ea06..0000000 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/api/transfer/PayTransferApiImpl.java +++ /dev/null @@ -1,49 +0,0 @@ -package cn.iocoder.yudao.module.pay.api.transfer; - -import cn.iocoder.yudao.framework.common.util.object.BeanUtils; -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; -import cn.iocoder.yudao.module.pay.dal.dataobject.channel.PayChannelDO; -import cn.iocoder.yudao.module.pay.dal.dataobject.transfer.PayTransferDO; -import cn.iocoder.yudao.module.pay.service.channel.PayChannelService; -import cn.iocoder.yudao.module.pay.service.transfer.PayTransferService; -import jakarta.annotation.Resource; -import org.springframework.stereotype.Service; -import org.springframework.validation.annotation.Validated; - -/** - * 转账单 API 实现类 - * - * @author jason - */ -@Service -@Validated -public class PayTransferApiImpl implements PayTransferApi { - - @Resource - private PayTransferService payTransferService; - @Resource - private PayChannelService payChannelService; - - @Override - public PayTransferCreateRespDTO createTransfer(PayTransferCreateReqDTO reqDTO) { - return payTransferService.createTransfer(reqDTO); - } - - @Override - public PayTransferRespDTO getTransfer(Long id) { - PayTransferDO transfer = payTransferService.getTransfer(id); - if (transfer == null) { - return null; - } - PayChannelDO channel = payChannelService.getChannel(transfer.getChannelId()); - String mchId = null; - if (channel != null && channel.getConfig() instanceof WxPayClientConfig) { - mchId = ((WxPayClientConfig) channel.getConfig()).getMchId(); - } - return BeanUtils.toBean(transfer, PayTransferRespDTO.class).setChannelMchId(mchId); - } - -} diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/api/wallet/PayWalletApiImpl.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/api/wallet/PayWalletApiImpl.java deleted file mode 100644 index 627cb5c..0000000 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/api/wallet/PayWalletApiImpl.java +++ /dev/null @@ -1,41 +0,0 @@ -package cn.iocoder.yudao.module.pay.api.wallet; - -import cn.hutool.core.lang.Assert; -import cn.iocoder.yudao.framework.common.util.object.BeanUtils; -import cn.iocoder.yudao.module.pay.api.wallet.dto.PayWalletAddBalanceReqDTO; -import cn.iocoder.yudao.module.pay.api.wallet.dto.PayWalletRespDTO; -import cn.iocoder.yudao.module.pay.dal.dataobject.wallet.PayWalletDO; -import cn.iocoder.yudao.module.pay.enums.wallet.PayWalletBizTypeEnum; -import cn.iocoder.yudao.module.pay.service.wallet.PayWalletService; -import jakarta.annotation.Resource; -import org.springframework.stereotype.Service; - -/** - * 钱包 API 实现类 - * - * @author 芋道源码 - */ -@Service -public class PayWalletApiImpl implements PayWalletApi { - - @Resource - private PayWalletService payWalletService; - - @Override - public void addWalletBalance(PayWalletAddBalanceReqDTO reqDTO) { - // 创建或获取钱包 - PayWalletDO wallet = payWalletService.getOrCreateWallet(reqDTO.getUserId(), reqDTO.getUserType()); - Assert.notNull(wallet, "钱包({}/{})不存在", reqDTO.getUserId(), reqDTO.getUserType()); - - // 增加余额 - PayWalletBizTypeEnum bizType = PayWalletBizTypeEnum.valueOf(reqDTO.getBizType()); - payWalletService.addWalletBalance(wallet.getId(), reqDTO.getBizId(), bizType, reqDTO.getPrice()); - } - - @Override - public PayWalletRespDTO getOrCreateWallet(Long userId, Integer userType) { - PayWalletDO wallet = payWalletService.getOrCreateWallet(userId, userType); - return BeanUtils.toBean(wallet, PayWalletRespDTO.class); - } - -} diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/app/PayAppController.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/app/PayAppController.java deleted file mode 100644 index a3e6d28..0000000 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/app/PayAppController.java +++ /dev/null @@ -1,108 +0,0 @@ -package cn.iocoder.yudao.module.pay.controller.admin.app; - -import cn.hutool.core.collection.CollUtil; -import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; -import cn.iocoder.yudao.framework.common.pojo.CommonResult; -import cn.iocoder.yudao.framework.common.pojo.PageResult; -import cn.iocoder.yudao.module.pay.controller.admin.app.vo.*; -import cn.iocoder.yudao.module.pay.convert.app.PayAppConvert; -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.service.app.PayAppService; -import cn.iocoder.yudao.module.pay.service.channel.PayChannelService; -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.Parameter; -import io.swagger.v3.oas.annotations.tags.Tag; -import jakarta.annotation.Resource; -import jakarta.validation.Valid; -import lombok.extern.slf4j.Slf4j; -import org.springframework.security.access.prepost.PreAuthorize; -import org.springframework.validation.annotation.Validated; -import org.springframework.web.bind.annotation.*; - -import java.util.List; - -import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; -import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList; - -@Slf4j -@Tag(name = "管理后台 - 支付应用信息") -@RestController -@RequestMapping("/pay/app") -@Validated -public class PayAppController { - - @Resource - private PayAppService appService; - @Resource - private PayChannelService channelService; - - @PostMapping("/create") - @Operation(summary = "创建支付应用信息") - @PreAuthorize("@ss.hasPermission('pay:app:create')") - public CommonResult createApp(@Valid @RequestBody PayAppCreateReqVO createReqVO) { - return success(appService.createApp(createReqVO)); - } - - @PutMapping("/update") - @Operation(summary = "更新支付应用信息") - @PreAuthorize("@ss.hasPermission('pay:app:update')") - public CommonResult updateApp(@Valid @RequestBody PayAppUpdateReqVO updateReqVO) { - appService.updateApp(updateReqVO); - return success(true); - } - - @PutMapping("/update-status") - @Operation(summary = "更新支付应用状态") - @PreAuthorize("@ss.hasPermission('pay:app:update')") - public CommonResult updateAppStatus(@Valid @RequestBody PayAppUpdateStatusReqVO updateReqVO) { - appService.updateAppStatus(updateReqVO.getId(), updateReqVO.getStatus()); - return success(true); - } - - @DeleteMapping("/delete") - @Operation(summary = "删除支付应用信息") - @Parameter(name = "id", description = "编号", required = true) - @PreAuthorize("@ss.hasPermission('pay:app:delete')") - public CommonResult deleteApp(@RequestParam("id") Long id) { - appService.deleteApp(id); - return success(true); - } - - @GetMapping("/get") - @Operation(summary = "获得支付应用信息") - @Parameter(name = "id", description = "编号", required = true, example = "1024") - @PreAuthorize("@ss.hasPermission('pay:app:query')") - public CommonResult getApp(@RequestParam("id") Long id) { - PayAppDO app = appService.getApp(id); - return success(PayAppConvert.INSTANCE.convert(app)); - } - - @GetMapping("/page") - @Operation(summary = "获得支付应用信息分页") - @PreAuthorize("@ss.hasPermission('pay:app:query')") - public CommonResult> getAppPage(@Valid PayAppPageReqVO pageVO) { - // 得到应用分页列表 - PageResult pageResult = appService.getAppPage(pageVO); - if (CollUtil.isEmpty(pageResult.getList())) { - return success(PageResult.empty()); - } - - // 得到所有的应用编号,查出所有的渠道,并移除未启用的渠道 - List channels = channelService.getChannelListByAppIds( - convertList(pageResult.getList(), PayAppDO::getId)); - channels.removeIf(channel -> !CommonStatusEnum.ENABLE.getStatus().equals(channel.getStatus())); - - // 拼接后返回 - return success(PayAppConvert.INSTANCE.convertPage(pageResult, channels)); - } - - @GetMapping("/list") - @Operation(summary = "获得应用列表") - @PreAuthorize("@ss.hasPermission('pay:merchant:query')") - public CommonResult> getAppList() { - List appListDO = appService.getAppList(); - return success(PayAppConvert.INSTANCE.convertList(appListDO)); - } - -} diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/app/vo/PayAppBaseVO.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/app/vo/PayAppBaseVO.java deleted file mode 100644 index 4fad3d1..0000000 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/app/vo/PayAppBaseVO.java +++ /dev/null @@ -1,47 +0,0 @@ -package cn.iocoder.yudao.module.pay.controller.admin.app.vo; -import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; -import cn.iocoder.yudao.framework.common.validation.InEnum; -import io.swagger.v3.oas.annotations.media.Schema; -import lombok.*; -import org.hibernate.validator.constraints.URL; - -import jakarta.validation.constraints.*; - -/** -* 支付应用信息 Base VO,提供给添加、修改、详细的子 VO 使用 -* 如果子 VO 存在差异的字段,请不要添加到这里,影响 Swagger 文档生成 -*/ -@Data -public class PayAppBaseVO { - - @Schema(description = "应用标识", requiredMode = Schema.RequiredMode.REQUIRED, example = "yudao") - @NotEmpty(message = "应用标识不能为空") - private String appKey; - - @Schema(description = "应用名", requiredMode = Schema.RequiredMode.REQUIRED, example = "小豆") - @NotNull(message = "应用名不能为空") - private String name; - - @Schema(description = "开启状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "0") - @NotNull(message = "开启状态不能为空") - @InEnum(CommonStatusEnum.class) - private Integer status; - - @Schema(description = "备注", example = "我是一个测试应用") - private String remark; - - @Schema(description = "支付结果的回调地址", requiredMode = Schema.RequiredMode.REQUIRED, example = "http://127.0.0.1:48080/pay-callback") - @NotNull(message = "支付结果的回调地址不能为空") - @URL(message = "支付结果的回调地址必须为 URL 格式") - private String orderNotifyUrl; - - @Schema(description = "退款结果的回调地址", requiredMode = Schema.RequiredMode.REQUIRED, example = "http://127.0.0.1:48080/refund-callback") - @NotNull(message = "退款结果的回调地址不能为空") - @URL(message = "退款结果的回调地址必须为 URL 格式") - private String refundNotifyUrl; - - @Schema(description = "转账结果的回调地址", example = "http://127.0.0.1:48080/transfer-callback") - @URL(message = "转账结果的回调地址必须为 URL 格式") - private String transferNotifyUrl; - -} diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/app/vo/PayAppCreateReqVO.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/app/vo/PayAppCreateReqVO.java deleted file mode 100644 index f1a5ddd..0000000 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/app/vo/PayAppCreateReqVO.java +++ /dev/null @@ -1,14 +0,0 @@ -package cn.iocoder.yudao.module.pay.controller.admin.app.vo; - -import io.swagger.v3.oas.annotations.media.Schema; -import lombok.Data; -import lombok.EqualsAndHashCode; -import lombok.ToString; - -@Schema(description = "管理后台 - 支付应用信息创建 Request VO") -@Data -@EqualsAndHashCode(callSuper = true) -@ToString(callSuper = true) -public class PayAppCreateReqVO extends PayAppBaseVO { - -} diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/app/vo/PayAppPageItemRespVO.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/app/vo/PayAppPageItemRespVO.java deleted file mode 100644 index 76b6200..0000000 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/app/vo/PayAppPageItemRespVO.java +++ /dev/null @@ -1,26 +0,0 @@ -package cn.iocoder.yudao.module.pay.controller.admin.app.vo; - -import io.swagger.v3.oas.annotations.media.Schema; -import lombok.Data; -import lombok.EqualsAndHashCode; -import lombok.ToString; - -import java.time.LocalDateTime; -import java.util.Set; - -@Schema(description = "管理后台 - 支付应用信息分页查询 Response VO,相比于支付信息,还会多出应用渠道的开关信息") -@Data -@EqualsAndHashCode(callSuper = true) -@ToString(callSuper = true) -public class PayAppPageItemRespVO extends PayAppBaseVO { - - @Schema(description = "应用编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") - private Long id; - - @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED) - private LocalDateTime createTime; - - @Schema(description = "已配置的支付渠道编码", requiredMode = Schema.RequiredMode.REQUIRED, example = "[alipay_pc, alipay_wap]") - private Set channelCodes; - -} diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/app/vo/PayAppPageReqVO.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/app/vo/PayAppPageReqVO.java deleted file mode 100644 index e433c85..0000000 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/app/vo/PayAppPageReqVO.java +++ /dev/null @@ -1,33 +0,0 @@ -package cn.iocoder.yudao.module.pay.controller.admin.app.vo; - -import cn.iocoder.yudao.framework.common.pojo.PageParam; -import io.swagger.v3.oas.annotations.media.Schema; -import lombok.Data; -import lombok.EqualsAndHashCode; -import lombok.ToString; -import org.springframework.format.annotation.DateTimeFormat; - -import java.time.LocalDateTime; - -import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND; - -@Schema(description = "管理后台 - 支付应用信息分页 Request VO") -@Data -@EqualsAndHashCode(callSuper = true) -@ToString(callSuper = true) -public class PayAppPageReqVO extends PageParam { - - @Schema(description = "应用名", example = "小豆") - private String name; - - @Schema(description = "应用标识", example = "yudao") - private String appKey; - - @Schema(description = "开启状态", example = "0") - private Integer status; - - @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) - @Schema(description = "创建时间") - private LocalDateTime[] createTime; - -} diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/app/vo/PayAppRespVO.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/app/vo/PayAppRespVO.java deleted file mode 100644 index 184e538..0000000 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/app/vo/PayAppRespVO.java +++ /dev/null @@ -1,25 +0,0 @@ -package cn.iocoder.yudao.module.pay.controller.admin.app.vo; - -import io.swagger.v3.oas.annotations.media.Schema; -import lombok.Data; -import lombok.EqualsAndHashCode; -import lombok.ToString; - -import java.time.LocalDateTime; - -@Schema(description = "管理后台 - 支付应用信息 Response VO") -@Data -@EqualsAndHashCode(callSuper = true) -@ToString(callSuper = true) -public class PayAppRespVO extends PayAppBaseVO { - - @Schema(description = "应用编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") - private Long id; - - @Schema(description = "应用标识", requiredMode = Schema.RequiredMode.REQUIRED, example = "yudao") - private String appKey; - - @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED) - private LocalDateTime createTime; - -} diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/app/vo/PayAppUpdateReqVO.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/app/vo/PayAppUpdateReqVO.java deleted file mode 100644 index c4e50bd..0000000 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/app/vo/PayAppUpdateReqVO.java +++ /dev/null @@ -1,17 +0,0 @@ -package cn.iocoder.yudao.module.pay.controller.admin.app.vo; - -import io.swagger.v3.oas.annotations.media.Schema; -import lombok.*; -import jakarta.validation.constraints.*; - -@Schema(description = "管理后台 - 支付应用信息更新 Request VO") -@Data -@EqualsAndHashCode(callSuper = true) -@ToString(callSuper = true) -public class PayAppUpdateReqVO extends PayAppBaseVO { - - @Schema(description = "应用编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") - @NotNull(message = "应用编号不能为空") - private Long id; - -} diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/app/vo/PayAppUpdateStatusReqVO.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/app/vo/PayAppUpdateStatusReqVO.java deleted file mode 100644 index 585a18f..0000000 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/app/vo/PayAppUpdateStatusReqVO.java +++ /dev/null @@ -1,20 +0,0 @@ -package cn.iocoder.yudao.module.pay.controller.admin.app.vo; - -import io.swagger.v3.oas.annotations.media.Schema; -import lombok.Data; - -import jakarta.validation.constraints.NotNull; - -@Schema(description = "管理后台 - 应用更新状态 Request VO") -@Data -public class PayAppUpdateStatusReqVO { - - @Schema(description = "应用编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") - @NotNull(message = "应用编号不能为空") - private Long id; - - @Schema(description = "状态,见 SysCommonStatusEnum 枚举", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") - @NotNull(message = "状态不能为空") - private Integer status; - -} diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/channel/PayChannelController.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/channel/PayChannelController.java deleted file mode 100644 index b7b0aed..0000000 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/channel/PayChannelController.java +++ /dev/null @@ -1,82 +0,0 @@ -package cn.iocoder.yudao.module.pay.controller.admin.channel; - -import cn.iocoder.yudao.framework.common.pojo.CommonResult; -import cn.iocoder.yudao.module.pay.controller.admin.channel.vo.PayChannelCreateReqVO; -import cn.iocoder.yudao.module.pay.controller.admin.channel.vo.PayChannelRespVO; -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.service.channel.PayChannelService; -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.Parameter; -import io.swagger.v3.oas.annotations.tags.Tag; -import org.springframework.security.access.prepost.PreAuthorize; -import org.springframework.validation.annotation.Validated; -import org.springframework.web.bind.annotation.*; - -import jakarta.annotation.Resource; -import jakarta.validation.Valid; -import java.util.List; -import java.util.Set; - -import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; -import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet; - -@Tag(name = "管理后台 - 支付渠道") -@RestController -@RequestMapping("/pay/channel") -@Validated -public class PayChannelController { - - @Resource - private PayChannelService channelService; - - @PostMapping("/create") - @Operation(summary = "创建支付渠道 ") - @PreAuthorize("@ss.hasPermission('pay:channel:create')") - public CommonResult createChannel(@Valid @RequestBody PayChannelCreateReqVO createReqVO) { - return success(channelService.createChannel(createReqVO)); - } - - @PutMapping("/update") - @Operation(summary = "更新支付渠道 ") - @PreAuthorize("@ss.hasPermission('pay:channel:update')") - public CommonResult updateChannel(@Valid @RequestBody PayChannelUpdateReqVO updateReqVO) { - channelService.updateChannel(updateReqVO); - return success(true); - } - - @DeleteMapping("/delete") - @Operation(summary = "删除支付渠道 ") - @Parameter(name = "id", description = "编号", required = true) - @PreAuthorize("@ss.hasPermission('pay:channel:delete')") - public CommonResult deleteChannel(@RequestParam("id") Long id) { - channelService.deleteChannel(id); - return success(true); - } - - @GetMapping("/get") - @Operation(summary = "获得支付渠道") - @Parameter(name = "id", description = "编号", required = true, example = "1024") - @PreAuthorize("@ss.hasPermission('pay:channel:query')") - public CommonResult getChannel(@RequestParam(value = "id", required = false) Long id, - @RequestParam(value = "appId", required = false) Long appId, - @RequestParam(value = "code", required = false) String code) { - PayChannelDO channel = null; - if (id != null) { - channel = channelService.getChannel(id); - } else if (appId != null && code != null) { - channel = channelService.getChannelByAppIdAndCode(appId, code); - } - return success(PayChannelConvert.INSTANCE.convert(channel)); - } - - @GetMapping("/get-enable-code-list") - @Operation(summary = "获得指定应用的开启的支付渠道编码列表") - @Parameter(name = "appId", description = "应用编号", required = true, example = "1") - public CommonResult> getEnableChannelCodeList(@RequestParam("appId") Long appId) { - List channels = channelService.getEnableChannelList(appId); - return success(convertSet(channels, PayChannelDO::getCode)); - } - -} diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/channel/vo/PayChannelBaseVO.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/channel/vo/PayChannelBaseVO.java deleted file mode 100644 index df99a3c..0000000 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/channel/vo/PayChannelBaseVO.java +++ /dev/null @@ -1,31 +0,0 @@ -package cn.iocoder.yudao.module.pay.controller.admin.channel.vo; -import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; -import cn.iocoder.yudao.framework.common.validation.InEnum; -import io.swagger.v3.oas.annotations.media.Schema; -import lombok.*; -import jakarta.validation.constraints.*; - -/** -* 支付渠道 Base VO,提供给添加、修改、详细的子 VO 使用 -* 如果子 VO 存在差异的字段,请不要添加到这里,影响 Swagger 文档生成 -*/ -@Data -public class PayChannelBaseVO { - - @Schema(description = "开启状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") - @NotNull(message = "开启状态不能为空") - @InEnum(CommonStatusEnum.class) - private Integer status; - - @Schema(description = "备注", example = "我是小备注") - private String remark; - - @Schema(description = "渠道费率,单位:百分比", requiredMode = Schema.RequiredMode.REQUIRED, example = "10") - @NotNull(message = "渠道费率,单位:百分比不能为空") - private Double feeRate; - - @Schema(description = "应用编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") - @NotNull(message = "应用编号不能为空") - private Long appId; - -} diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/channel/vo/PayChannelCreateReqVO.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/channel/vo/PayChannelCreateReqVO.java deleted file mode 100644 index ccb0497..0000000 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/channel/vo/PayChannelCreateReqVO.java +++ /dev/null @@ -1,25 +0,0 @@ -package cn.iocoder.yudao.module.pay.controller.admin.channel.vo; - -import io.swagger.v3.oas.annotations.media.Schema; -import lombok.Data; -import lombok.EqualsAndHashCode; -import lombok.ToString; - -import jakarta.validation.constraints.NotBlank; -import jakarta.validation.constraints.NotNull; - -@Schema(description = "管理后台 - 支付渠道 创建 Request VO") -@Data -@EqualsAndHashCode(callSuper = true) -@ToString(callSuper = true) -public class PayChannelCreateReqVO extends PayChannelBaseVO { - - @Schema(description = "渠道编码", requiredMode = Schema.RequiredMode.REQUIRED, example = "alipay_pc") - @NotNull(message = "渠道编码不能为空") - private String code; - - @Schema(description = "渠道配置的 json 字符串") - @NotBlank(message = "渠道配置不能为空") - private String config; - -} diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/channel/vo/PayChannelRespVO.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/channel/vo/PayChannelRespVO.java deleted file mode 100644 index dafd29e..0000000 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/channel/vo/PayChannelRespVO.java +++ /dev/null @@ -1,25 +0,0 @@ -package cn.iocoder.yudao.module.pay.controller.admin.channel.vo; -import io.swagger.v3.oas.annotations.media.Schema; -import lombok.*; - -import java.time.LocalDateTime; - -@Schema(description = "管理后台 - 支付渠道 Response VO") -@Data -@EqualsAndHashCode(callSuper = true) -@ToString(callSuper = true) -public class PayChannelRespVO extends PayChannelBaseVO { - - @Schema(description = "商户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") - private Long id; - - @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") - private LocalDateTime createTime; - - @Schema(description = "渠道编码", requiredMode = Schema.RequiredMode.REQUIRED, example = "alipay_pc") - private String code; - - @Schema(description = "配置", requiredMode = Schema.RequiredMode.REQUIRED) - private String config; - -} diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/channel/vo/PayChannelUpdateReqVO.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/channel/vo/PayChannelUpdateReqVO.java deleted file mode 100644 index 9998528..0000000 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/channel/vo/PayChannelUpdateReqVO.java +++ /dev/null @@ -1,20 +0,0 @@ -package cn.iocoder.yudao.module.pay.controller.admin.channel.vo; -import io.swagger.v3.oas.annotations.media.Schema; -import lombok.*; -import jakarta.validation.constraints.*; - -@Schema(description = "管理后台 - 支付渠道 更新 Request VO") -@Data -@EqualsAndHashCode(callSuper = true) -@ToString(callSuper = true) -public class PayChannelUpdateReqVO extends PayChannelBaseVO { - - @Schema(description = "商户编号", requiredMode = Schema.RequiredMode.REQUIRED) - @NotNull(message = "商户编号不能为空") - private Long id; - - @Schema(description = "渠道配置的json字符串") - @NotBlank(message = "渠道配置不能为空") - private String config; - -} diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/demo/PayDemoOrderController.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/demo/PayDemoOrderController.java deleted file mode 100644 index f571d88..0000000 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/demo/PayDemoOrderController.java +++ /dev/null @@ -1,76 +0,0 @@ -package cn.iocoder.yudao.module.pay.controller.admin.demo; - -import cn.iocoder.yudao.framework.common.pojo.CommonResult; -import cn.iocoder.yudao.framework.common.pojo.PageParam; -import cn.iocoder.yudao.framework.common.pojo.PageResult; -import cn.iocoder.yudao.framework.common.util.object.BeanUtils; -import cn.iocoder.yudao.module.pay.api.notify.dto.PayOrderNotifyReqDTO; -import cn.iocoder.yudao.module.pay.api.notify.dto.PayRefundNotifyReqDTO; -import cn.iocoder.yudao.module.pay.controller.admin.demo.vo.order.PayDemoOrderCreateReqVO; -import cn.iocoder.yudao.module.pay.controller.admin.demo.vo.order.PayDemoOrderRespVO; -import cn.iocoder.yudao.module.pay.dal.dataobject.demo.PayDemoOrderDO; -import cn.iocoder.yudao.module.pay.service.demo.PayDemoOrderService; -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.Parameter; -import io.swagger.v3.oas.annotations.tags.Tag; -import jakarta.annotation.Resource; -import jakarta.annotation.security.PermitAll; -import jakarta.validation.Valid; -import org.springframework.validation.annotation.Validated; -import org.springframework.web.bind.annotation.*; - -import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; -import static cn.iocoder.yudao.framework.common.util.servlet.ServletUtils.getClientIP; -import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId; - -@Tag(name = "管理后台 - 示例订单") // 目的:演示支付、退款功能 -@RestController -@RequestMapping("/pay/demo-order") -@Validated -public class PayDemoOrderController { - - @Resource - private PayDemoOrderService payDemoOrderService; - - @PostMapping("/create") - @Operation(summary = "创建示例订单") - public CommonResult createDemoOrder(@Valid @RequestBody PayDemoOrderCreateReqVO createReqVO) { - return success(payDemoOrderService.createDemoOrder(getLoginUserId(), createReqVO)); - } - - @GetMapping("/page") - @Operation(summary = "获得示例订单分页") - public CommonResult> getDemoOrderPage(@Valid PageParam pageVO) { - PageResult pageResult = payDemoOrderService.getDemoOrderPage(pageVO); - return success(BeanUtils.toBean(pageResult, PayDemoOrderRespVO.class)); - } - - @PostMapping("/update-paid") - @Operation(summary = "更新示例订单为已支付") // 由 pay-module 支付服务,进行回调,可见 PayNotifyJob - @PermitAll // 无需登录,安全由 PayDemoOrderService 内部校验实现 - public CommonResult updateDemoOrderPaid(@RequestBody PayOrderNotifyReqDTO notifyReqDTO) { - payDemoOrderService.updateDemoOrderPaid(Long.valueOf(notifyReqDTO.getMerchantOrderId()), - notifyReqDTO.getPayOrderId()); - return success(true); - } - - @PutMapping("/refund") - @Operation(summary = "发起示例订单的退款") - @Parameter(name = "id", description = "编号", required = true, example = "1024") - public CommonResult refundDemoOrder(@RequestParam("id") Long id) { - payDemoOrderService.refundDemoOrder(id, getClientIP()); - return success(true); - } - - @PostMapping("/update-refunded") - @Operation(summary = "更新示例订单为已退款") // 由 pay-module 支付服务,进行回调,可见 PayNotifyJob - @PermitAll // 无需登录,安全由 PayDemoOrderService 内部校验实现 - public CommonResult updateDemoOrderRefunded(@RequestBody PayRefundNotifyReqDTO notifyReqDTO) { - payDemoOrderService.updateDemoOrderRefunded( - Long.valueOf(notifyReqDTO.getMerchantOrderId()), - notifyReqDTO.getMerchantRefundId(), - notifyReqDTO.getPayRefundId()); - return success(true); - } - -} diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/demo/PayDemoWithdrawController.http b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/demo/PayDemoWithdrawController.http deleted file mode 100644 index 4162baa..0000000 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/demo/PayDemoWithdrawController.http +++ /dev/null @@ -1,50 +0,0 @@ -### 请求 /pay/demo-withdraw/create 接口(支付宝) -POST {{baseUrl}}/pay/demo-withdraw/create -Authorization: Bearer {{token}} -Content-Type: application/json -tenant-id: {{adminTenantId}} - -{ - "type": 1, - "subject": "测试转账", - "price": 10, - "userAccount": "oespxk7368@sandbox.com", - "userName": "oespxk7368" -} - -### 请求 /pay/demo-withdraw/create 接口(微信余额) -POST {{baseUrl}}/pay/demo-withdraw/create -Authorization: Bearer {{token}} -Content-Type: application/json -tenant-id: {{adminTenantId}} - -{ - "type": 2, - "subject": "测试转账", - "price": 1, - "userAccount": "oiSC85elO_OZogXODC5RoGyXamK4", - "userName": "芋艿" -} - -### 请求 /pay/demo-withdraw/create 接口(钱包余额) -POST {{baseUrl}}/pay/demo-withdraw/create -Authorization: Bearer {{token}} -Content-Type: application/json -tenant-id: {{adminTenantId}} - -{ - "type": 3, - "subject": "测试转账", - "price": 1, - "userAccount": "1" -} - -### 请求 /pay/demo-withdraw/transfer 接口(发起转账) -POST {{baseUrl}}/pay/demo-withdraw/transfer?id=1 -Authorization: Bearer {{token}} -tenant-id: {{adminTenantId}} - -### 请求 /pay/demo-withdraw/page 接口(查询分页) -GET {{baseUrl}}/pay/demo-withdraw/page?pageNo=1&pageSize=10 -Authorization: Bearer {{token}} -tenant-id: {{adminTenantId}} \ No newline at end of file diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/demo/PayDemoWithdrawController.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/demo/PayDemoWithdrawController.java deleted file mode 100644 index 033bc95..0000000 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/demo/PayDemoWithdrawController.java +++ /dev/null @@ -1,63 +0,0 @@ -package cn.iocoder.yudao.module.pay.controller.admin.demo; - -import cn.iocoder.yudao.framework.common.pojo.CommonResult; -import cn.iocoder.yudao.framework.common.pojo.PageParam; -import cn.iocoder.yudao.framework.common.pojo.PageResult; -import cn.iocoder.yudao.framework.common.util.object.BeanUtils; -import cn.iocoder.yudao.module.pay.api.notify.dto.PayTransferNotifyReqDTO; -import cn.iocoder.yudao.module.pay.controller.admin.demo.vo.withdraw.PayDemoWithdrawCreateReqVO; -import cn.iocoder.yudao.module.pay.controller.admin.demo.vo.withdraw.PayDemoWithdrawRespVO; -import cn.iocoder.yudao.module.pay.dal.dataobject.demo.PayDemoWithdrawDO; -import cn.iocoder.yudao.module.pay.service.demo.PayDemoWithdrawService; -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.Parameter; -import io.swagger.v3.oas.annotations.tags.Tag; -import jakarta.annotation.Resource; -import jakarta.annotation.security.PermitAll; -import jakarta.validation.Valid; -import org.springframework.validation.annotation.Validated; -import org.springframework.web.bind.annotation.*; - -import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; - -@Tag(name = "管理后台 - 示例提现订单") // 目的:演示转账功能 -@RestController -@RequestMapping("/pay/demo-withdraw") -@Validated -public class PayDemoWithdrawController { - - @Resource - private PayDemoWithdrawService demoWithdrawService; - - @PostMapping("/create") - @Operation(summary = "创建示例提现单") - public CommonResult createDemoWithdraw(@Valid @RequestBody PayDemoWithdrawCreateReqVO createReqVO) { - Long id = demoWithdrawService.createDemoWithdraw(createReqVO); - return success(id); - } - - @PostMapping("/transfer") - @Operation(summary = "提现单转账") - @Parameter(name = "id", required = true, description = "提现单编号", example = "1024") - public CommonResult transferDemoWithdraw(@RequestParam("id") Long id) { - Long payTransferId = demoWithdrawService.transferDemoWithdraw(id); - return success(payTransferId); - } - - @GetMapping("/page") - @Operation(summary = "获得示例提现单分页") - public CommonResult> getDemoWithdrawPage(@Valid PageParam pageVO) { - PageResult pageResult = demoWithdrawService.getDemoWithdrawPage(pageVO); - return success(BeanUtils.toBean(pageResult, PayDemoWithdrawRespVO.class)); - } - - @PostMapping("/update-transferred") - @Operation(summary = "更新示例提现单的转账状态") // 由 pay-module 转账服务,进行回调 - @PermitAll // 无需登录,安全由 PayDemoTransferService 内部校验实现 - public CommonResult updateDemoWithdrawTransferred(@RequestBody PayTransferNotifyReqDTO notifyReqDTO) { - demoWithdrawService.updateDemoWithdrawTransferred(Long.valueOf(notifyReqDTO.getMerchantTransferId()), - notifyReqDTO.getPayTransferId()); - return success(true); - } - -} diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/demo/vo/order/PayDemoOrderCreateReqVO.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/demo/vo/order/PayDemoOrderCreateReqVO.java deleted file mode 100644 index 27ca07b..0000000 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/demo/vo/order/PayDemoOrderCreateReqVO.java +++ /dev/null @@ -1,16 +0,0 @@ -package cn.iocoder.yudao.module.pay.controller.admin.demo.vo.order; - -import io.swagger.v3.oas.annotations.media.Schema; -import lombok.Data; - -import jakarta.validation.constraints.NotNull; - -@Schema(description = "管理后台 - 示例订单创建 Request VO") -@Data -public class PayDemoOrderCreateReqVO { - - @Schema(description = "商品编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "17682") - @NotNull(message = "商品编号不能为空") - private Long spuId; - -} diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/demo/vo/order/PayDemoOrderRespVO.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/demo/vo/order/PayDemoOrderRespVO.java deleted file mode 100644 index cb30563..0000000 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/demo/vo/order/PayDemoOrderRespVO.java +++ /dev/null @@ -1,54 +0,0 @@ -package cn.iocoder.yudao.module.pay.controller.admin.demo.vo.order; - -import io.swagger.v3.oas.annotations.media.Schema; -import lombok.*; - -import java.time.LocalDateTime; - -/** -* 示例订单 Base VO,提供给添加、修改、详细的子 VO 使用 -* 如果子 VO 存在差异的字段,请不要添加到这里,影响 Swagger 文档生成 -*/ -@Data -public class PayDemoOrderRespVO { - - @Schema(description = "订单编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") - private Long id; - - @Schema(description = "用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "23199") - private Long userId; - - @Schema(description = "商品编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "17682") - private Long spuId; - - @Schema(description = "商家备注", example = "李四") - private String spuName; - - @Schema(description = "价格,单位:分", requiredMode = Schema.RequiredMode.REQUIRED, example = "30381") - private Integer price; - - @Schema(description = "是否已支付", requiredMode = Schema.RequiredMode.REQUIRED) - private Boolean payStatus; - - @Schema(description = "支付订单编号", example = "16863") - private Long payOrderId; - - @Schema(description = "订单支付时间") - private LocalDateTime payTime; - - @Schema(description = "支付渠道", example = "alipay_qr") - private String payChannelCode; - - @Schema(description = "支付退款编号", example = "23366") - private Long payRefundId; - - @Schema(description = "退款金额,单位:分", requiredMode = Schema.RequiredMode.REQUIRED, example = "14039") - private Integer refundPrice; - - @Schema(description = "退款时间") - private LocalDateTime refundTime; - - @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED) - private LocalDateTime createTime; - -} diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/demo/vo/withdraw/PayDemoWithdrawCreateReqVO.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/demo/vo/withdraw/PayDemoWithdrawCreateReqVO.java deleted file mode 100644 index 3c6c1c8..0000000 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/demo/vo/withdraw/PayDemoWithdrawCreateReqVO.java +++ /dev/null @@ -1,42 +0,0 @@ -package cn.iocoder.yudao.module.pay.controller.admin.demo.vo.withdraw; - -import cn.hutool.core.util.ObjectUtil; -import cn.iocoder.yudao.framework.common.validation.InEnum; -import cn.iocoder.yudao.module.pay.enums.demo.PayDemoWithdrawTypeEnum; -import io.swagger.v3.oas.annotations.media.Schema; -import jakarta.validation.constraints.*; -import lombok.Data; - -@Schema(description = "管理后台 - 示例提现单创建 Request VO") -@Data -public class PayDemoWithdrawCreateReqVO { - - @Schema(description = "提现标题", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋艿是一种菜") - @NotEmpty(message = "提现标题不能为空") - private String subject; - - @Schema(description = "提现金额,单位:分", requiredMode = Schema.RequiredMode.REQUIRED, example = "100") - @NotNull(message = "提现金额不能为空") - @Min(value = 1, message = "提现金额必须大于零") - private Integer price; - - @Schema(description = "收款人账号", requiredMode= Schema.RequiredMode.REQUIRED, example = "test1") - @NotBlank(message = "收款人账号不能为空") - private String userAccount; - - @Schema(description = "收款人姓名", example = "test1") - private String userName; - - @Schema(description = "提现方式", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") - @NotNull(message = "提现方式不能为空") - @InEnum(PayDemoWithdrawTypeEnum.class) - private Integer type; - - @AssertTrue(message = "收款人姓名") - public boolean isUserNameValid() { - // 特殊:支付宝必须填写用户名!!! - return ObjectUtil.notEqual(type, PayDemoWithdrawTypeEnum.ALIPAY.getType()) - || ObjectUtil.isNotEmpty(userName); - } - -} diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/demo/vo/withdraw/PayDemoWithdrawRespVO.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/demo/vo/withdraw/PayDemoWithdrawRespVO.java deleted file mode 100644 index 3abc607..0000000 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/demo/vo/withdraw/PayDemoWithdrawRespVO.java +++ /dev/null @@ -1,47 +0,0 @@ -package cn.iocoder.yudao.module.pay.controller.admin.demo.vo.withdraw; - -import io.swagger.v3.oas.annotations.media.Schema; -import lombok.Data; - -import java.time.LocalDateTime; - -@Schema(description = "管理后台 - 示例转账单创建 Request VO") -@Data -public class PayDemoWithdrawRespVO { - - @Schema(description = "转账单编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") - private Long id; - - @Schema(description = "提现标题", requiredMode = Schema.RequiredMode.REQUIRED, example = "吃饭报销") - private String subject; - - @Schema(description = "提现金额,单位:分", requiredMode = Schema.RequiredMode.REQUIRED, example = "22338") - private Integer price; - - @Schema(description = "收款人姓名", example = "test") - private String userName; - - @Schema(description = "收款人账号", example = "32167") - private String userAccount; - - @Schema(description = "提现类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") - private Integer type; - - @Schema(description = "提现状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "2") - private Integer status; - - // ========== 转账相关字段 ========== - - @Schema(description = "转账单编号", example = "23695") - private Long payTransferId; - - @Schema(description = "转账渠道", example = "wx_lite") - private String transferChannelCode; - - @Schema(description = "转账成功时间") - private LocalDateTime transferTime; - - @Schema(description = "转账失败原因", example = "IP 不正确") - private String transferErrorMsg; - -} 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 deleted file mode 100644 index bcf97ea..0000000 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/notify/PayNotifyController.java +++ /dev/null @@ -1,169 +0,0 @@ -package cn.iocoder.yudao.module.pay.controller.admin.notify; - -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.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; -import cn.iocoder.yudao.module.pay.controller.admin.notify.vo.PayNotifyTaskRespVO; -import cn.iocoder.yudao.module.pay.dal.dataobject.app.PayAppDO; -import cn.iocoder.yudao.module.pay.dal.dataobject.notify.PayNotifyLogDO; -import cn.iocoder.yudao.module.pay.dal.dataobject.notify.PayNotifyTaskDO; -import cn.iocoder.yudao.module.pay.service.app.PayAppService; -import cn.iocoder.yudao.module.pay.service.channel.PayChannelService; -import cn.iocoder.yudao.module.pay.service.notify.PayNotifyService; -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 io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.Parameter; -import io.swagger.v3.oas.annotations.tags.Tag; -import lombok.extern.slf4j.Slf4j; -import org.springframework.security.access.prepost.PreAuthorize; -import org.springframework.validation.annotation.Validated; -import org.springframework.web.bind.annotation.*; - -import jakarta.annotation.Resource; -import jakarta.annotation.security.PermitAll; -import jakarta.validation.Valid; -import java.util.List; -import java.util.Map; - -import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; -import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; -import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList; -import static cn.iocoder.yudao.module.pay.enums.ErrorCodeConstants.CHANNEL_NOT_FOUND; - -@Tag(name = "管理后台 - 回调通知") -@RestController -@RequestMapping("/pay/notify") -@Validated -@Slf4j -public class PayNotifyController { - - @Resource - private PayOrderService orderService; - @Resource - private PayRefundService refundService; - @Resource - private PayTransferService payTransferService; - @Resource - private PayNotifyService notifyService; - @Resource - private PayAppService appService; - @Resource - private PayChannelService channelService; - - @PostMapping(value = "/order/{channelId}") - @Operation(summary = "支付渠道的统一【支付】回调") - @PermitAll - @TenantIgnore - public String notifyOrder(@PathVariable("channelId") Long channelId, - @RequestParam(required = false) Map params, - @RequestBody(required = false) String body, - @RequestHeader Map headers) { - log.info("[notifyOrder][channelId({}) 回调数据({}/{})]", channelId, params, body); - // 1. 校验支付渠道是否存在 - PayClient payClient = channelService.getPayClient(channelId); - if (payClient == null) { - log.error("[notifyOrder][渠道编号({}) 找不到对应的支付客户端]", channelId); - throw exception(CHANNEL_NOT_FOUND); - } - - // 2. 解析通知数据 - PayOrderRespDTO notify = payClient.parseOrderNotify(params, body, headers); - orderService.notifyOrder(channelId, notify); - return "success"; - } - - @PostMapping(value = "/refund/{channelId}") - @Operation(summary = "支付渠道的统一【退款】回调") - @PermitAll - @TenantIgnore - public String notifyRefund(@PathVariable("channelId") Long channelId, - @RequestParam(required = false) Map params, - @RequestBody(required = false) String body, - @RequestHeader Map headers) { - log.info("[notifyRefund][channelId({}) 回调数据({}/{})]", channelId, params, body); - // 1. 校验支付渠道是否存在 - PayClient payClient = channelService.getPayClient(channelId); - if (payClient == null) { - log.error("[notifyRefund][渠道编号({}) 找不到对应的支付客户端]", channelId); - throw exception(CHANNEL_NOT_FOUND); - } - - // 2. 解析通知数据 - PayRefundRespDTO notify = payClient.parseRefundNotify(params, body, headers); - refundService.notifyRefund(channelId, notify); - return "success"; - } - - @PostMapping(value = "/transfer/{channelId}") - @Operation(summary = "支付渠道的统一【转账】回调") - @PermitAll - @TenantIgnore - public String notifyTransfer(@PathVariable("channelId") Long channelId, - @RequestParam(required = false) Map params, - @RequestBody(required = false) String body, - @RequestHeader Map headers) { - log.info("[notifyTransfer][channelId({}) 回调数据({}/{})]", channelId, params, body); - // 1. 校验支付渠道是否存在 - PayClient payClient = channelService.getPayClient(channelId); - if (payClient == null) { - log.error("[notifyTransfer][渠道编号({}) 找不到对应的支付客户端]", channelId); - throw exception(CHANNEL_NOT_FOUND); - } - - // 2. 解析通知数据 - PayTransferRespDTO notify = payClient.parseTransferNotify(params, body, headers); - payTransferService.notifyTransfer(channelId, notify); - return "success"; - } - - @GetMapping("/get-detail") - @Operation(summary = "获得回调通知的明细") - @Parameter(name = "id", description = "编号", required = true, example = "1024") - @PreAuthorize("@ss.hasPermission('pay:notify:query')") - public CommonResult getNotifyTaskDetail(@RequestParam("id") Long id) { - PayNotifyTaskDO task = notifyService.getNotifyTask(id); - if (task == null) { - return success(null); - } - // 拼接返回 - PayAppDO app = appService.getApp(task.getAppId()); - List logs = notifyService.getNotifyLogList(id); - return success(BeanUtils.toBean(task, PayNotifyTaskDetailRespVO.class, respVO -> { - if (app != null) { - respVO.setAppName(app.getName()); - } - respVO.setLogs(BeanUtils.toBean(logs, PayNotifyTaskDetailRespVO.Log.class)); - })); - } - - @GetMapping("/page") - @Operation(summary = "获得回调通知分页") - @PreAuthorize("@ss.hasPermission('pay:notify:query')") - public CommonResult> getNotifyTaskPage(@Valid PayNotifyTaskPageReqVO pageVO) { - PageResult pageResult = notifyService.getNotifyTaskPage(pageVO); - if (CollUtil.isEmpty(pageResult.getList())) { - return success(PageResult.empty()); - } - // 拼接返回 - Map apps = appService.getAppMap(convertList(pageResult.getList(), PayNotifyTaskDO::getAppId)); - - // 转换对象 - return success(BeanUtils.toBean(pageResult, PayNotifyTaskRespVO.class, order -> { - PayAppDO app = apps.get(order.getAppId()); - if (app != null) { - order.setAppName(app.getName()); - } - })); - } - -} diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/notify/vo/PayNotifyTaskDetailRespVO.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/notify/vo/PayNotifyTaskDetailRespVO.java deleted file mode 100644 index 739cedf..0000000 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/notify/vo/PayNotifyTaskDetailRespVO.java +++ /dev/null @@ -1,42 +0,0 @@ - -package cn.iocoder.yudao.module.pay.controller.admin.notify.vo; - -import io.swagger.v3.oas.annotations.media.Schema; -import lombok.Data; -import lombok.EqualsAndHashCode; -import lombok.ToString; - -import java.time.LocalDateTime; -import java.util.List; - -@Schema(description = "管理后台 - 回调通知的明细 Response VO") -@Data -@EqualsAndHashCode(callSuper = true) -@ToString(callSuper = true) -public class PayNotifyTaskDetailRespVO extends PayNotifyTaskRespVO { - - @Schema(description = "回调日志列表") - private List logs; - - @Schema(description = "管理后台 - 回调日志") - @Data - public static class Log { - - @Schema(description = "日志编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "8848") - private Long id; - - @Schema(description = "通知状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") - private Byte status; - - @Schema(description = "当前通知次数", requiredMode = Schema.RequiredMode.REQUIRED) - private Byte notifyTimes; - - @Schema(description = "HTTP 响应结果", requiredMode = Schema.RequiredMode.REQUIRED) - private String response; - - @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED) - private LocalDateTime createTime; - - } - -} diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/notify/vo/PayNotifyTaskPageReqVO.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/notify/vo/PayNotifyTaskPageReqVO.java deleted file mode 100644 index f6dbc84..0000000 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/notify/vo/PayNotifyTaskPageReqVO.java +++ /dev/null @@ -1,45 +0,0 @@ -package cn.iocoder.yudao.module.pay.controller.admin.notify.vo; - -import cn.iocoder.yudao.framework.common.pojo.PageParam; -import io.swagger.v3.oas.annotations.media.Schema; -import lombok.Data; -import lombok.EqualsAndHashCode; -import lombok.ToString; -import org.springframework.format.annotation.DateTimeFormat; - -import java.time.LocalDateTime; - -import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND; - -@Schema(description = "管理后台 - 回调通知分页 Request VO") -@Data -@EqualsAndHashCode(callSuper = true) -@ToString(callSuper = true) -public class PayNotifyTaskPageReqVO extends PageParam { - - @Schema(description = "应用编号", example = "10636") - private Long appId; - - @Schema(description = "通知类型", example = "2") - private Integer type; - - @Schema(description = "数据编号", example = "6722") - private Long dataId; - - @Schema(description = "通知状态", example = "1") - private Integer status; - - @Schema(description = "商户订单编号", example = "26697") - private String merchantOrderId; - - @Schema(description = "商户退款编号", example = "26697") - private String merchantRefundId; - - @Schema(description = "商户转账编号", example = "26697") - private String merchantTransferId; - - @Schema(description = "创建时间") - @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) - private LocalDateTime[] createTime; - -} diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/notify/vo/PayNotifyTaskRespVO.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/notify/vo/PayNotifyTaskRespVO.java deleted file mode 100644 index 75228b1..0000000 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/notify/vo/PayNotifyTaskRespVO.java +++ /dev/null @@ -1,56 +0,0 @@ -package cn.iocoder.yudao.module.pay.controller.admin.notify.vo; - -import io.swagger.v3.oas.annotations.media.Schema; -import lombok.*; -import java.time.LocalDateTime; - -@Schema(description = "管理后台 - 回调通知 Response VO") -@Data -public class PayNotifyTaskRespVO { - - @Schema(description = "任务编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "3380") - private Long id; - - @Schema(description = "应用编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "10636") - private Long appId; - - @Schema(description = "应用名称", example = "wx_pay") - private String appName; - - @Schema(description = "通知类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "2") - private Byte type; - - @Schema(description = "数据编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "6722") - private Long dataId; - - @Schema(description = "商户订单编号", example = "26697") - private String merchantOrderId; - - @Schema(description = "商户退款编号", example = "26697") - private String merchantRefundId; - - @Schema(description = "商户转账编号", example = "26697") - private String merchantTransferId; - - @Schema(description = "通知状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") - private Byte status; - - @Schema(description = "下一次通知时间", requiredMode = Schema.RequiredMode.REQUIRED) - private LocalDateTime nextNotifyTime; - - @Schema(description = "最后一次执行时间", requiredMode = Schema.RequiredMode.REQUIRED) - private LocalDateTime lastExecuteTime; - - @Schema(description = "当前通知次数", requiredMode = Schema.RequiredMode.REQUIRED) - private Byte notifyTimes; - - @Schema(description = "最大可通知次数", requiredMode = Schema.RequiredMode.REQUIRED) - private Byte maxNotifyTimes; - - @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED) - private LocalDateTime createTime; - - @Schema(description = "更新时间", requiredMode = Schema.RequiredMode.REQUIRED) - private LocalDateTime updateTime; - -} 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 deleted file mode 100755 index 97bd29f..0000000 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/order/PayOrderController.java +++ /dev/null @@ -1,145 +0,0 @@ -package cn.iocoder.yudao.module.pay.controller.admin.order; - -import cn.hutool.core.collection.CollectionUtil; -import cn.iocoder.yudao.framework.apilog.core.annotation.ApiAccessLog; -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.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.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; -import com.google.common.collect.Maps; -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.Parameter; -import io.swagger.v3.oas.annotations.Parameters; -import io.swagger.v3.oas.annotations.tags.Tag; -import jakarta.annotation.Resource; -import jakarta.servlet.http.HttpServletResponse; -import jakarta.validation.Valid; -import org.springframework.security.access.prepost.PreAuthorize; -import org.springframework.validation.annotation.Validated; -import org.springframework.web.bind.annotation.*; - -import java.io.IOException; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.Objects; - -import static cn.iocoder.yudao.framework.apilog.core.enums.OperateTypeEnum.EXPORT; -import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; -import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList; -import static cn.iocoder.yudao.framework.common.util.servlet.ServletUtils.getClientIP; -import static cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils.getLoginUserId; -import static cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils.getLoginUserType; - -@Tag(name = "管理后台 - 支付订单") -@RestController -@RequestMapping("/pay/order") -@Validated -public class PayOrderController { - - @Resource - private PayOrderService orderService; - @Resource - private PayAppService appService; - @Resource - private PayWalletService payWalletService; - - @GetMapping("/get") - @Operation(summary = "获得支付订单") - @Parameters({ - @Parameter(name = "id", description = "编号", required = true, example = "1024"), - @Parameter(name = "sync", description = "是否同步", example = "true") - }) - @PreAuthorize("@ss.hasPermission('pay:order:query')") - public CommonResult getOrder(@RequestParam("id") Long id, - @RequestParam(value = "sync", required = false) Boolean sync) { - PayOrderDO order = orderService.getOrder(id); - // sync 仅在等待支付 - if (Boolean.TRUE.equals(sync) && PayOrderStatusEnum.isWaiting(order.getStatus())) { - orderService.syncOrderQuietly(order.getId()); - // 重新查询,因为同步后,可能会有变化 - order = orderService.getOrder(id); - } - return success(BeanUtils.toBean(order, PayOrderRespVO.class)); - } - - @GetMapping("/get-detail") - @Operation(summary = "获得支付订单详情") - @Parameter(name = "id", description = "编号", required = true, example = "1024") - @PreAuthorize("@ss.hasPermission('pay:order:query')") - public CommonResult getOrderDetail(@RequestParam("id") Long id) { - PayOrderDO order = orderService.getOrder(id); - if (order == null) { - return success(null); - } - - // 拼接返回 - PayAppDO app = appService.getApp(order.getAppId()); - PayOrderExtensionDO orderExtension = orderService.getOrderExtension(order.getExtensionId()); - return success(PayOrderConvert.INSTANCE.convert(order, orderExtension, app)); - } - - @PostMapping("/submit") - @Operation(summary = "提交支付订单") - public CommonResult submitPayOrder(@RequestBody PayOrderSubmitReqVO reqVO) { - // 1. 钱包支付事,需要额外传 user_id 和 user_type - if (Objects.equals(reqVO.getChannelCode(), PayChannelEnum.WALLET.getCode())) { - if (reqVO.getChannelExtras() == null) { - reqVO.setChannelExtras(Maps.newHashMapWithExpectedSize(1)); - } - PayWalletDO wallet = payWalletService.getOrCreateWallet(getLoginUserId(), getLoginUserType()); - reqVO.getChannelExtras().put(WalletPayClient.WALLET_ID_KEY, String.valueOf(wallet.getId())); - } - - // 2. 提交支付 - PayOrderSubmitRespVO respVO = orderService.submitOrder(reqVO, getClientIP()); - return success(respVO); - } - - @GetMapping("/page") - @Operation(summary = "获得支付订单分页") - @PreAuthorize("@ss.hasPermission('pay:order:query')") - public CommonResult> getOrderPage(@Valid PayOrderPageReqVO pageVO) { - PageResult pageResult = orderService.getOrderPage(pageVO); - if (CollectionUtil.isEmpty(pageResult.getList())) { - return success(new PageResult<>(pageResult.getTotal())); - } - - // 拼接返回 - Map appMap = appService.getAppMap(convertList(pageResult.getList(), PayOrderDO::getAppId)); - return success(PayOrderConvert.INSTANCE.convertPage(pageResult, appMap)); - } - - @GetMapping("/export-excel") - @Operation(summary = "导出支付订单 Excel") - @PreAuthorize("@ss.hasPermission('pay:order:export')") - @ApiAccessLog(operateType = EXPORT) - public void exportOrderExcel(@Valid PayOrderExportReqVO exportReqVO, - HttpServletResponse response) throws IOException { - List list = orderService.getOrderList(exportReqVO); - if (CollectionUtil.isEmpty(list)) { - ExcelUtils.write(response, "支付订单.xls", "数据", - PayOrderExcelVO.class, new ArrayList<>()); - return; - } - - // 拼接返回 - Map appMap = appService.getAppMap(convertList(list, PayOrderDO::getAppId)); - List excelList = PayOrderConvert.INSTANCE.convertList(list, appMap); - // 导出 Excel - ExcelUtils.write(response, "支付订单.xls", "数据", PayOrderExcelVO.class, excelList); - } - -} diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/order/vo/PayOrderBaseVO.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/order/vo/PayOrderBaseVO.java deleted file mode 100755 index 8cb5b80..0000000 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/order/vo/PayOrderBaseVO.java +++ /dev/null @@ -1,89 +0,0 @@ -package cn.iocoder.yudao.module.pay.controller.admin.order.vo; -import io.swagger.v3.oas.annotations.media.Schema; -import lombok.Data; -import org.springframework.format.annotation.DateTimeFormat; - -import jakarta.validation.constraints.NotNull; -import java.time.LocalDateTime; - -import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND; - -/** - * 支付订单 Base VO,提供给添加、修改、详细的子 VO 使用 - * 如果子 VO 存在差异的字段,请不要添加到这里,影响 Swagger 文档生成 - * - * @author aquan - */ -@Data -public class PayOrderBaseVO { - - @Schema(description = "应用编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") - @NotNull(message = "应用编号不能为空") - private Long appId; - - @Schema(description = "渠道编号", example = "2048") - private Long channelId; - - @Schema(description = "渠道编码", example = "wx_app") - private String channelCode; - - @Schema(description = "商户订单编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "888") - @NotNull(message = "商户订单编号不能为空") - private String merchantOrderId; - - @Schema(description = "商品标题", requiredMode = Schema.RequiredMode.REQUIRED, example = "土豆") - @NotNull(message = "商品标题不能为空") - private String subject; - - @Schema(description = "商品描述", requiredMode = Schema.RequiredMode.REQUIRED, example = "我是土豆") - @NotNull(message = "商品描述不能为空") - private String body; - - @Schema(description = "异步通知地址", requiredMode = Schema.RequiredMode.REQUIRED, example = "http://127.0.0.1:48080/pay/notify") - @NotNull(message = "异步通知地址不能为空") - private String notifyUrl; - - @Schema(description = "支付金额,单位:分", requiredMode = Schema.RequiredMode.REQUIRED, example = "10") - @NotNull(message = "支付金额,单位:分不能为空") - private Long price; - - @Schema(description = "渠道手续费,单位:百分比", example = "10") - private Double channelFeeRate; - - @Schema(description = "渠道手续金额,单位:分", example = "100") - private Integer channelFeePrice; - - @Schema(description = "支付状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") - @NotNull(message = "支付状态不能为空") - private Integer status; - - @Schema(description = "用户 IP", requiredMode = Schema.RequiredMode.REQUIRED, example = "127.0.0.1") - @NotNull(message = "用户 IP不能为空") - private String userIp; - - @Schema(description = "订单失效时间", requiredMode = Schema.RequiredMode.REQUIRED) - @NotNull(message = "订单失效时间不能为空") - @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) - private LocalDateTime expireTime; - - @Schema(description = "订单支付成功时间") - @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) - private LocalDateTime successTime; - - @Schema(description = "支付成功的订单拓展单编号", example = "50") - private Long extensionId; - - @Schema(description = "支付订单号", example = "2048888") - private String no; - - @Schema(description = "退款总金额,单位:分", requiredMode = Schema.RequiredMode.REQUIRED, example = "10") - @NotNull(message = "退款总金额,单位:分不能为空") - private Long refundPrice; - - @Schema(description = "渠道用户编号", example = "2048") - private String channelUserId; - - @Schema(description = "渠道订单号", example = "4096") - private String channelOrderNo; - -} diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/order/vo/PayOrderDetailsRespVO.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/order/vo/PayOrderDetailsRespVO.java deleted file mode 100644 index a4fe463..0000000 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/order/vo/PayOrderDetailsRespVO.java +++ /dev/null @@ -1,45 +0,0 @@ -package cn.iocoder.yudao.module.pay.controller.admin.order.vo; - -import io.swagger.v3.oas.annotations.media.Schema; -import lombok.Data; -import lombok.EqualsAndHashCode; -import lombok.ToString; - -import java.time.LocalDateTime; - -@Schema(description = "管理后台 - 支付订单详细信息 Response VO") -@Data -@EqualsAndHashCode(callSuper = true) -@ToString(callSuper = true) -public class PayOrderDetailsRespVO extends PayOrderBaseVO { - - @Schema(description = "支付订单编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") - private Long id; - - @Schema(description = "应用名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋道源码") - private String appName; - - @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED) - private LocalDateTime createTime; - - @Schema(description = "更新时间", requiredMode = Schema.RequiredMode.REQUIRED) - private LocalDateTime updateTime; - - /** - * 支付订单扩展 - */ - private PayOrderExtension extension; - - @Data - @Schema(description = "支付订单扩展") - public static class PayOrderExtension { - - @Schema(description = "支付订单号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") - private String no; - - @Schema(description = "支付异步通知的内容") - private String channelNotifyData; - - } - -} diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/order/vo/PayOrderExcelVO.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/order/vo/PayOrderExcelVO.java deleted file mode 100755 index 5dc17a0..0000000 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/order/vo/PayOrderExcelVO.java +++ /dev/null @@ -1,67 +0,0 @@ -package cn.iocoder.yudao.module.pay.controller.admin.order.vo; - -import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat; -import cn.iocoder.yudao.framework.excel.core.convert.DictConvert; -import cn.iocoder.yudao.framework.excel.core.convert.MoneyConvert; -import cn.iocoder.yudao.module.pay.enums.DictTypeConstants; -import com.alibaba.excel.annotation.ExcelProperty; -import lombok.Data; - -import java.time.LocalDateTime; - -/** - * 支付订单 Excel VO - * - * @author aquan - */ -@Data -public class PayOrderExcelVO { - - @ExcelProperty("编号") - private Long id; - - @ExcelProperty("创建时间") - private LocalDateTime createTime; - - @ExcelProperty(value = "支付金额", converter = MoneyConvert.class) - private Integer price; - - @ExcelProperty(value = "退款金额", converter = MoneyConvert.class) - private Integer refundPrice; - - @ExcelProperty(value = "手续金额", converter = MoneyConvert.class) - private Integer channelFeePrice; - - @ExcelProperty("商户单号") - private String merchantOrderId; - - @ExcelProperty(value = "支付单号") - private String no; - - @ExcelProperty("渠道单号") - private String channelOrderNo; - - @ExcelProperty(value = "支付状态", converter = DictConvert.class) - @DictFormat(DictTypeConstants.ORDER_STATUS) - private Integer status; - - @ExcelProperty(value = "渠道编号名称", converter = DictConvert.class) - @DictFormat(DictTypeConstants.CHANNEL_CODE) - private String channelCode; - - @ExcelProperty("订单支付成功时间") - private LocalDateTime successTime; - - @ExcelProperty("订单失效时间") - private LocalDateTime expireTime; - - @ExcelProperty(value = "应用名称") - private String appName; - - @ExcelProperty("商品标题") - private String subject; - - @ExcelProperty("商品描述") - private String body; - -} diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/order/vo/PayOrderExportReqVO.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/order/vo/PayOrderExportReqVO.java deleted file mode 100755 index 9e4d20b..0000000 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/order/vo/PayOrderExportReqVO.java +++ /dev/null @@ -1,37 +0,0 @@ -package cn.iocoder.yudao.module.pay.controller.admin.order.vo; - -import io.swagger.v3.oas.annotations.media.Schema; -import lombok.Data; -import org.springframework.format.annotation.DateTimeFormat; - -import java.time.LocalDateTime; - -import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND; - -@Schema(description = "管理后台 - 支付订单 Excel 导出 Request VO,参数和 PayOrderPageReqVO 是一致的") -@Data -public class PayOrderExportReqVO { - - @Schema(description = "应用编号", example = "1024") - private Long appId; - - @Schema(description = "渠道编码", example = "wx_app") - private String channelCode; - - @Schema(description = "商户订单编号", example = "4096") - private String merchantOrderId; - - @Schema(description = "渠道编号", example = "1888") - private String channelOrderNo; - - @Schema(description = "支付单号", example = "2014888") - private String no; - - @Schema(description = "支付状态", example = "0") - private Integer status; - - @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) - @Schema(description = "创建时间") - private LocalDateTime[] createTime; - -} diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/order/vo/PayOrderPageItemRespVO.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/order/vo/PayOrderPageItemRespVO.java deleted file mode 100755 index 05411c9..0000000 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/order/vo/PayOrderPageItemRespVO.java +++ /dev/null @@ -1,25 +0,0 @@ -package cn.iocoder.yudao.module.pay.controller.admin.order.vo; - -import io.swagger.v3.oas.annotations.media.Schema; -import lombok.Data; -import lombok.EqualsAndHashCode; -import lombok.ToString; - -import java.time.LocalDateTime; - -@Schema(description = "管理后台 - 支付订单分页 Request VO") -@Data -@EqualsAndHashCode(callSuper = true) -@ToString(callSuper = true) -public class PayOrderPageItemRespVO extends PayOrderBaseVO { - - @Schema(description = "支付订单编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") - private Long id; - - @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED) - private LocalDateTime createTime; - - @Schema(description = "应用名称", example = "wx_pay") - private String appName; - -} diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/order/vo/PayOrderPageReqVO.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/order/vo/PayOrderPageReqVO.java deleted file mode 100755 index f7ff801..0000000 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/order/vo/PayOrderPageReqVO.java +++ /dev/null @@ -1,42 +0,0 @@ -package cn.iocoder.yudao.module.pay.controller.admin.order.vo; - -import cn.iocoder.yudao.framework.common.pojo.PageParam; -import io.swagger.v3.oas.annotations.media.Schema; -import lombok.Data; -import lombok.EqualsAndHashCode; -import lombok.ToString; -import org.springframework.format.annotation.DateTimeFormat; - -import java.time.LocalDateTime; - -import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND; - -@Schema(description = "管理后台 - 支付订单分页 Request VO") -@Data -@EqualsAndHashCode(callSuper = true) -@ToString(callSuper = true) -public class PayOrderPageReqVO extends PageParam { - - @Schema(description = "应用编号", example = "1024") - private Long appId; - - @Schema(description = "渠道编码", example = "wx_app") - private String channelCode; - - @Schema(description = "商户订单编号", example = "4096") - private String merchantOrderId; - - @Schema(description = "渠道编号", example = "1888") - private String channelOrderNo; - - @Schema(description = "支付单号", example = "2014888") - private String no; - - @Schema(description = "支付状态", example = "0") - private Integer status; - - @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) - @Schema(description = "创建时间") - private LocalDateTime[] createTime; - -} diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/order/vo/PayOrderRespVO.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/order/vo/PayOrderRespVO.java deleted file mode 100644 index d48a4db..0000000 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/order/vo/PayOrderRespVO.java +++ /dev/null @@ -1,22 +0,0 @@ -package cn.iocoder.yudao.module.pay.controller.admin.order.vo; - -import io.swagger.v3.oas.annotations.media.Schema; -import lombok.Data; -import lombok.EqualsAndHashCode; -import lombok.ToString; - -import java.time.LocalDateTime; - -@Schema(description = "管理后台 - 支付订单 Response VO") -@Data -@EqualsAndHashCode(callSuper = true) -@ToString(callSuper = true) -public class PayOrderRespVO extends PayOrderBaseVO { - - @Schema(description = "支付订单编号", requiredMode = Schema.RequiredMode.REQUIRED) - private Long id; - - @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED) - private LocalDateTime createTime; - -} diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/order/vo/PayOrderSubmitReqVO.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/order/vo/PayOrderSubmitReqVO.java deleted file mode 100644 index 3fc7fc9..0000000 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/order/vo/PayOrderSubmitReqVO.java +++ /dev/null @@ -1,33 +0,0 @@ -package cn.iocoder.yudao.module.pay.controller.admin.order.vo; - -import io.swagger.v3.oas.annotations.media.Schema; -import lombok.Data; -import org.hibernate.validator.constraints.URL; - -import jakarta.validation.constraints.NotEmpty; -import jakarta.validation.constraints.NotNull; -import java.util.Map; - -@Schema(description = "管理后台 - 支付订单提交 Request VO") -@Data -public class PayOrderSubmitReqVO { - - @Schema(description = "支付单编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") - @NotNull(message = "支付单编号不能为空") - private Long id; - - @Schema(description = "支付渠道", requiredMode = Schema.RequiredMode.REQUIRED, example = "wx_pub") - @NotEmpty(message = "支付渠道不能为空") - private String channelCode; - - @Schema(description = "支付渠道的额外参数,例如说,微信公众号需要传递 openid 参数") - private Map channelExtras; - - @Schema(description = "展示模式", example = "url") // 参见 {@link PayDisplayModeEnum} 枚举。如果不传递,则每个支付渠道使用默认的方式 - private String displayMode; - - @Schema(description = "回跳地址") - @URL(message = "回跳地址的格式必须是 URL") - private String returnUrl; - -} diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/order/vo/PayOrderSubmitRespVO.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/order/vo/PayOrderSubmitRespVO.java deleted file mode 100644 index 8dcd9df..0000000 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/order/vo/PayOrderSubmitRespVO.java +++ /dev/null @@ -1,18 +0,0 @@ -package cn.iocoder.yudao.module.pay.controller.admin.order.vo; - -import io.swagger.v3.oas.annotations.media.Schema; -import lombok.Data; - -@Schema(description = "管理后台 - 支付订单提交 Response VO") -@Data -public class PayOrderSubmitRespVO { - - @Schema(description = "支付状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "10") // 参见 PayOrderStatusEnum 枚举 - private Integer status; - - @Schema(description = "展示模式", requiredMode = Schema.RequiredMode.REQUIRED, example = "url") // 参见 PayDisplayModeEnum 枚举 - private String displayMode; - @Schema(description = "展示内容", requiredMode = Schema.RequiredMode.REQUIRED) - private String displayContent; - -} diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/refund/PayRefundController.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/refund/PayRefundController.java deleted file mode 100755 index 76fb045..0000000 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/refund/PayRefundController.java +++ /dev/null @@ -1,90 +0,0 @@ -package cn.iocoder.yudao.module.pay.controller.admin.refund; - -import cn.hutool.core.collection.CollectionUtil; -import cn.iocoder.yudao.framework.apilog.core.annotation.ApiAccessLog; -import cn.iocoder.yudao.framework.common.pojo.CommonResult; -import cn.iocoder.yudao.framework.common.pojo.PageResult; -import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils; -import cn.iocoder.yudao.module.pay.controller.admin.refund.vo.*; -import cn.iocoder.yudao.module.pay.convert.refund.PayRefundConvert; -import cn.iocoder.yudao.module.pay.dal.dataobject.app.PayAppDO; -import cn.iocoder.yudao.module.pay.dal.dataobject.refund.PayRefundDO; -import cn.iocoder.yudao.module.pay.service.app.PayAppService; -import cn.iocoder.yudao.module.pay.service.refund.PayRefundService; -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.Parameter; -import io.swagger.v3.oas.annotations.tags.Tag; -import jakarta.annotation.Resource; -import jakarta.servlet.http.HttpServletResponse; -import jakarta.validation.Valid; -import org.springframework.security.access.prepost.PreAuthorize; -import org.springframework.validation.annotation.Validated; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestParam; -import org.springframework.web.bind.annotation.RestController; - -import java.io.IOException; -import java.util.List; -import java.util.Map; - -import static cn.iocoder.yudao.framework.apilog.core.enums.OperateTypeEnum.EXPORT; -import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; -import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList; - -@Tag(name = "管理后台 - 退款订单") -@RestController -@RequestMapping("/pay/refund") -@Validated -public class PayRefundController { - - @Resource - private PayRefundService refundService; - @Resource - private PayAppService appService; - - @GetMapping("/get") - @Operation(summary = "获得退款订单") - @Parameter(name = "id", description = "编号", required = true, example = "1024") - @PreAuthorize("@ss.hasPermission('pay:refund:query')") - public CommonResult getRefund(@RequestParam("id") Long id) { - PayRefundDO refund = refundService.getRefund(id); - if (refund == null) { - return success(new PayRefundDetailsRespVO()); - } - - // 拼接数据 - PayAppDO app = appService.getApp(refund.getAppId()); - return success(PayRefundConvert.INSTANCE.convert(refund, app)); - } - - @GetMapping("/page") - @Operation(summary = "获得退款订单分页") - @PreAuthorize("@ss.hasPermission('pay:refund:query')") - public CommonResult> getRefundPage(@Valid PayRefundPageReqVO pageVO) { - PageResult pageResult = refundService.getRefundPage(pageVO); - if (CollectionUtil.isEmpty(pageResult.getList())) { - return success(new PageResult<>(pageResult.getTotal())); - } - - // 处理应用ID数据 - Map appMap = appService.getAppMap(convertList(pageResult.getList(), PayRefundDO::getAppId)); - return success(PayRefundConvert.INSTANCE.convertPage(pageResult, appMap)); - } - - @GetMapping("/export-excel") - @Operation(summary = "导出退款订单 Excel") - @PreAuthorize("@ss.hasPermission('pay:refund:export')") - @ApiAccessLog(operateType = EXPORT) - public void exportRefundExcel(@Valid PayRefundExportReqVO exportReqVO, - HttpServletResponse response) throws IOException { - List list = refundService.getRefundList(exportReqVO); - - // 拼接返回 - Map appMap = appService.getAppMap(convertList(list, PayRefundDO::getAppId)); - List excelList = PayRefundConvert.INSTANCE.convertList(list, appMap); - // 导出 Excel - ExcelUtils.write(response, "退款订单.xls", "数据", PayRefundExcelVO.class, excelList); - } - -} diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/refund/vo/PayRefundBaseVO.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/refund/vo/PayRefundBaseVO.java deleted file mode 100755 index 25cadf4..0000000 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/refund/vo/PayRefundBaseVO.java +++ /dev/null @@ -1,78 +0,0 @@ -package cn.iocoder.yudao.module.pay.controller.admin.refund.vo; - -import io.swagger.v3.oas.annotations.media.Schema; -import lombok.Data; - -import java.time.LocalDateTime; - -/** -* 退款订单 Base VO,提供给添加、修改、详细的子 VO 使用 -* 如果子 VO 存在差异的字段,请不要添加到这里,影响 Swagger 文档生成 -*/ -@Data -public class PayRefundBaseVO { - - @Schema(description = "外部退款号", requiredMode = Schema.RequiredMode.REQUIRED, example = "110") - private String no; - - @Schema(description = "应用编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") - private Long appId; - - @Schema(description = "渠道编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2048") - private Long channelId; - - @Schema(description = "渠道编码", requiredMode = Schema.RequiredMode.REQUIRED, example = "wx_app") - private String channelCode; - - @Schema(description = "订单编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") - private Long orderId; - - // ========== 商户相关字段 ========== - - @Schema(description = "商户订单编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "225") - private String merchantOrderId; - - @Schema(description = "商户退款订单号", requiredMode = Schema.RequiredMode.REQUIRED, example = "512") - private String merchantRefundId; - - @Schema(description = "异步通知地址", requiredMode = Schema.RequiredMode.REQUIRED) - private String notifyUrl; - - // ========== 退款相关字段 ========== - - @Schema(description = "退款状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "0") - private Integer status; - - @Schema(description = "支付金额", requiredMode = Schema.RequiredMode.REQUIRED, example = "100") - private Long payPrice; - - @Schema(description = "退款金额,单位分", requiredMode = Schema.RequiredMode.REQUIRED, example = "200") - private Long refundPrice; - - @Schema(description = "退款原因", requiredMode = Schema.RequiredMode.REQUIRED, example = "我要退了") - private String reason; - - @Schema(description = "用户 IP", requiredMode = Schema.RequiredMode.REQUIRED, example = "127.0.0.1") - private String userIp; - - // ========== 渠道相关字段 ========== - - @Schema(description = "渠道订单号", requiredMode = Schema.RequiredMode.REQUIRED, example = "233") - private String channelOrderNo; - - @Schema(description = "渠道退款单号", example = "2022") - private String channelRefundNo; - - @Schema(description = "退款成功时间") - private LocalDateTime successTime; - - @Schema(description = "调用渠道的错误码") - private String channelErrorCode; - - @Schema(description = "调用渠道的错误提示") - private String channelErrorMsg; - - @Schema(description = "支付渠道的额外参数") - private String channelNotifyData; - -} diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/refund/vo/PayRefundDetailsRespVO.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/refund/vo/PayRefundDetailsRespVO.java deleted file mode 100755 index 8f50a3a..0000000 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/refund/vo/PayRefundDetailsRespVO.java +++ /dev/null @@ -1,40 +0,0 @@ -package cn.iocoder.yudao.module.pay.controller.admin.refund.vo; - -import io.swagger.v3.oas.annotations.media.Schema; -import lombok.Data; -import lombok.EqualsAndHashCode; -import lombok.ToString; - -import java.time.LocalDateTime; - -@Schema(description = "管理后台 - 退款订单详情 Response VO") -@Data -@EqualsAndHashCode(callSuper = true) -@ToString(callSuper = true) -public class PayRefundDetailsRespVO extends PayRefundBaseVO { - - @Schema(description = "支付退款编号", requiredMode = Schema.RequiredMode.REQUIRED) - private Long id; - - @Schema(description = "应用名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "我是芋艿") - private String appName; - - @Schema(description = "支付订单", requiredMode = Schema.RequiredMode.REQUIRED) - private Order order; - - @Schema(description = "创建时间") - private LocalDateTime createTime; - - @Schema(description = "更新时间") - private LocalDateTime updateTime; - - @Schema(description = "管理后台 - 支付订单") - @Data - public static class Order { - - @Schema(description = "商品标题", requiredMode = Schema.RequiredMode.REQUIRED, example = "土豆") - private String subject; - - } - -} diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/refund/vo/PayRefundExcelVO.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/refund/vo/PayRefundExcelVO.java deleted file mode 100755 index 758b6b6..0000000 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/refund/vo/PayRefundExcelVO.java +++ /dev/null @@ -1,61 +0,0 @@ -package cn.iocoder.yudao.module.pay.controller.admin.refund.vo; - -import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat; -import cn.iocoder.yudao.framework.excel.core.convert.DictConvert; -import cn.iocoder.yudao.framework.excel.core.convert.MoneyConvert; -import cn.iocoder.yudao.module.pay.enums.DictTypeConstants; -import com.alibaba.excel.annotation.ExcelProperty; -import lombok.Data; - -import java.time.LocalDateTime; - -/** - * 退款订单 Excel VO - * - * @author aquan - */ -@Data -public class PayRefundExcelVO { - - @ExcelProperty("支付退款编号") - private Long id; - - @ExcelProperty("创建时间") - private LocalDateTime createTime; - - @ExcelProperty(value = "支付金额", converter = MoneyConvert.class) - private Integer payPrice; - - @ExcelProperty(value = "退款金额", converter = MoneyConvert.class) - private Integer refundPrice; - - @ExcelProperty("商户退款单号") - private String merchantRefundId; - @ExcelProperty("退款单号") - private String no; - @ExcelProperty("渠道退款单号") - private String channelRefundNo; - - @ExcelProperty("商户支付单号") - private String merchantOrderId; - @ExcelProperty("渠道支付单号") - private String channelOrderNo; - - @ExcelProperty(value = "退款状态", converter = DictConvert.class) - @DictFormat(DictTypeConstants.REFUND_STATUS) - private Integer status; - - @ExcelProperty(value = "退款渠道", converter = DictConvert.class) - @DictFormat(DictTypeConstants.CHANNEL_CODE) - private String channelCode; - - @ExcelProperty("成功时间") - private LocalDateTime successTime; - - @ExcelProperty(value = "支付应用") - private String appName; - - @ExcelProperty("退款原因") - private String reason; - -} diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/refund/vo/PayRefundExportReqVO.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/refund/vo/PayRefundExportReqVO.java deleted file mode 100755 index 645816e..0000000 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/refund/vo/PayRefundExportReqVO.java +++ /dev/null @@ -1,40 +0,0 @@ -package cn.iocoder.yudao.module.pay.controller.admin.refund.vo; - -import io.swagger.v3.oas.annotations.media.Schema; -import lombok.Data; -import org.springframework.format.annotation.DateTimeFormat; - -import java.time.LocalDateTime; - -import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND; - -@Schema(description = "管理后台 - 退款订单 Excel 导出 Request VO,参数和 PayRefundPageReqVO 是一致的") -@Data -public class PayRefundExportReqVO { - - @Schema(description = "应用编号", example = "1024") - private Long appId; - - @Schema(description = "渠道编码", example = "wx_app") - private String channelCode; - - @Schema(description = "商户支付单号", example = "10") - private String merchantOrderId; - - @Schema(description = "商户退款单号", example = "20") - private String merchantRefundId; - - @Schema(description = "渠道支付单号", example = "30") - private String channelOrderNo; - - @Schema(description = "渠道退款单号", example = "40") - private String channelRefundNo; - - @Schema(description = "退款状态", example = "0") - private Integer status; - - @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) - @Schema(description = "创建时间") - private LocalDateTime[] createTime; - -} diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/refund/vo/PayRefundPageItemRespVO.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/refund/vo/PayRefundPageItemRespVO.java deleted file mode 100755 index 27c1285..0000000 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/refund/vo/PayRefundPageItemRespVO.java +++ /dev/null @@ -1,25 +0,0 @@ -package cn.iocoder.yudao.module.pay.controller.admin.refund.vo; - -import io.swagger.v3.oas.annotations.media.Schema; -import lombok.Data; -import lombok.EqualsAndHashCode; -import lombok.ToString; - -import java.time.LocalDateTime; - -@Schema(description = "管理后台 - 退款订单分页查询 Response VO") -@Data -@EqualsAndHashCode(callSuper = true) -@ToString(callSuper = true) -public class PayRefundPageItemRespVO extends PayRefundBaseVO { - - @Schema(description = "支付订单编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") - private Long id; - - @Schema(description = "应用名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "我是芋艿") - private String appName; - - @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED) - private LocalDateTime createTime; - -} diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/refund/vo/PayRefundPageReqVO.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/refund/vo/PayRefundPageReqVO.java deleted file mode 100755 index 7ff1530..0000000 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/refund/vo/PayRefundPageReqVO.java +++ /dev/null @@ -1,45 +0,0 @@ -package cn.iocoder.yudao.module.pay.controller.admin.refund.vo; - -import cn.iocoder.yudao.framework.common.pojo.PageParam; -import io.swagger.v3.oas.annotations.media.Schema; -import lombok.Data; -import lombok.EqualsAndHashCode; -import lombok.ToString; -import org.springframework.format.annotation.DateTimeFormat; - -import java.time.LocalDateTime; - -import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND; - -@Schema(description = "管理后台 - 退款订单分页 Request VO") -@Data -@EqualsAndHashCode(callSuper = true) -@ToString(callSuper = true) -public class PayRefundPageReqVO extends PageParam { - - @Schema(description = "应用编号", example = "1024") - private Long appId; - - @Schema(description = "渠道编码", example = "wx_app") - private String channelCode; - - @Schema(description = "商户支付单号", example = "10") - private String merchantOrderId; - - @Schema(description = "商户退款单号", example = "20") - private String merchantRefundId; - - @Schema(description = "渠道支付单号", example = "30") - private String channelOrderNo; - - @Schema(description = "渠道退款单号", example = "40") - private String channelRefundNo; - - @Schema(description = "退款状态", example = "0") - private Integer status; - - @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) - @Schema(description = "创建时间") - private LocalDateTime[] createTime; - -} diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/transfer/PayTransferController.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/transfer/PayTransferController.java deleted file mode 100644 index cfc30b2..0000000 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/transfer/PayTransferController.java +++ /dev/null @@ -1,91 +0,0 @@ -package cn.iocoder.yudao.module.pay.controller.admin.transfer; - -import cn.iocoder.yudao.framework.apilog.core.annotation.ApiAccessLog; -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.module.pay.controller.admin.transfer.vo.PayTransferPageReqVO; -import cn.iocoder.yudao.module.pay.controller.admin.transfer.vo.PayTransferRespVO; -import cn.iocoder.yudao.module.pay.dal.dataobject.app.PayAppDO; -import cn.iocoder.yudao.module.pay.dal.dataobject.transfer.PayTransferDO; -import cn.iocoder.yudao.module.pay.service.app.PayAppService; -import cn.iocoder.yudao.module.pay.service.transfer.PayTransferService; -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.tags.Tag; -import jakarta.annotation.Resource; -import jakarta.servlet.http.HttpServletResponse; -import jakarta.validation.Valid; -import org.springframework.security.access.prepost.PreAuthorize; -import org.springframework.validation.annotation.Validated; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestParam; -import org.springframework.web.bind.annotation.RestController; - -import java.io.IOException; -import java.util.Map; - -import static cn.iocoder.yudao.framework.apilog.core.enums.OperateTypeEnum.EXPORT; -import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; -import static cn.iocoder.yudao.framework.common.pojo.PageParam.PAGE_SIZE_NONE; -import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList; - -@Tag(name = "管理后台 - 转账单") -@RestController -@RequestMapping("/pay/transfer") -@Validated -public class PayTransferController { - - @Resource - private PayTransferService payTransferService; - @Resource - private PayAppService payAppService; - - @GetMapping("/get") - @Operation(summary = "获得转账订单") - @PreAuthorize("@ss.hasPermission('pay:transfer:query')") - public CommonResult getTransfer(@RequestParam("id") Long id) { - PayTransferDO transfer = payTransferService.getTransfer(id); - if (transfer == null) { - return success(new PayTransferRespVO()); - } - - // 拼接数据 - PayAppDO app = payAppService.getApp(transfer.getAppId()); - return success(BeanUtils.toBean(transfer, PayTransferRespVO.class, transferVO -> { - if (app != null) { - transferVO.setAppName(app.getName()); - } - })); - } - - @GetMapping("/page") - @Operation(summary = "获得转账订单分页") - @PreAuthorize("@ss.hasPermission('pay:transfer:query')") - public CommonResult> getTransferPage(@Valid PayTransferPageReqVO pageVO) { - PageResult pageResult = payTransferService.getTransferPage(pageVO); - - // 拼接数据 - Map apps = payAppService.getAppMap(convertList(pageResult.getList(), PayTransferDO::getAppId)); - return success(BeanUtils.toBean(pageResult, PayTransferRespVO.class, transferVO -> { - if (apps.containsKey(transferVO.getAppId())) { - transferVO.setAppName(apps.get(transferVO.getAppId()).getName()); - } - })); - } - - @GetMapping("/export-excel") - @Operation(summary = "导出转账订单 Excel") - @PreAuthorize("@ss.hasPermission('pay:transfer:export')") - @ApiAccessLog(operateType = EXPORT) - public void exportTransfer(PayTransferPageReqVO pageReqVO, - HttpServletResponse response) throws IOException { - pageReqVO.setPageSize(PAGE_SIZE_NONE); - PageResult pageResult = getTransferPage(pageReqVO).getData(); - - // 导出 Excel - ExcelUtils.write(response, "转账订单.xls", "数据", PayTransferRespVO.class, pageResult.getList()); - } - -} diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/transfer/vo/PayTransferCreateRespVO.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/transfer/vo/PayTransferCreateRespVO.java deleted file mode 100644 index 9cb44bf..0000000 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/transfer/vo/PayTransferCreateRespVO.java +++ /dev/null @@ -1,16 +0,0 @@ -package cn.iocoder.yudao.module.pay.controller.admin.transfer.vo; - -import io.swagger.v3.oas.annotations.media.Schema; -import lombok.Data; - -@Schema(description = "管理后台 - 发起转账 Response VO") -@Data -public class PayTransferCreateRespVO { - - @Schema(description = "转账单编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") - private Long id; - - @Schema(description = "转账状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") // 参见 PayTransferStatusEnum 枚举 - private Integer status; - -} diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/transfer/vo/PayTransferPageReqVO.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/transfer/vo/PayTransferPageReqVO.java deleted file mode 100644 index 08a6d45..0000000 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/transfer/vo/PayTransferPageReqVO.java +++ /dev/null @@ -1,45 +0,0 @@ -package cn.iocoder.yudao.module.pay.controller.admin.transfer.vo; - -import lombok.*; -import io.swagger.v3.oas.annotations.media.Schema; -import cn.iocoder.yudao.framework.common.pojo.PageParam; -import org.springframework.format.annotation.DateTimeFormat; -import java.time.LocalDateTime; - -import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND; - -@Schema(description = "管理后台 - 转账单分页 Request VO") -@Data -@EqualsAndHashCode(callSuper = true) -@ToString(callSuper = true) -public class PayTransferPageReqVO extends PageParam { - - @Schema(description = "转账单号") - private String no; - - @Schema(description = "应用编号", example = "12831") - private Long appId; - - @Schema(description = "渠道编码", example = "wx_app") - private String channelCode; - - @Schema(description = "商户转账单编号", example = "17481") - private String merchantOrderId; - - @Schema(description = "转账状态", example = "2") - private Integer status; - - @Schema(description = "收款人姓名", example = "王五") - private String userName; - - @Schema(description = "收款人账号", example = "26589") - private String userAccount; - - @Schema(description = "渠道转账单号") - private String channelTransferNo; - - @Schema(description = "创建时间") - @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) - private LocalDateTime[] createTime; - -} diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/transfer/vo/PayTransferRespVO.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/transfer/vo/PayTransferRespVO.java deleted file mode 100644 index 9e98db1..0000000 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/transfer/vo/PayTransferRespVO.java +++ /dev/null @@ -1,100 +0,0 @@ -package cn.iocoder.yudao.module.pay.controller.admin.transfer.vo; - -import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat; -import cn.iocoder.yudao.framework.excel.core.convert.DictConvert; -import cn.iocoder.yudao.framework.excel.core.convert.MoneyConvert; -import cn.iocoder.yudao.module.pay.enums.DictTypeConstants; - -import com.alibaba.excel.annotation.ExcelIgnoreUnannotated; -import com.alibaba.excel.annotation.ExcelProperty; -import io.swagger.v3.oas.annotations.media.Schema; -import lombok.*; -import java.time.LocalDateTime; -import java.util.Map; - -@Schema(description = "管理后台 - 转账单 Response VO") -@Data -@ExcelIgnoreUnannotated -public class PayTransferRespVO { - - @ExcelProperty("转账单编号") - @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2931") - private Long id; - - @ExcelProperty("转账单号") - @Schema(description = "转账单号", requiredMode = Schema.RequiredMode.REQUIRED) - private String no; - - @Schema(description = "应用编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "12831") - private Long appId; - - @ExcelProperty("应用名称") - @Schema(description = "应用名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋艿") - private String appName; - - @Schema(description = "转账渠道编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "24833") - private Long channelId; - - @ExcelProperty(value = "转账渠道", converter = DictConvert.class) - @DictFormat(DictTypeConstants.CHANNEL_CODE) - @Schema(description = "转账渠道编码", requiredMode = Schema.RequiredMode.REQUIRED) - private String channelCode; - - @ExcelProperty("商户转账单编号") - @Schema(description = "商户转账单编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "17481") - private String merchantTransferId; - - @ExcelProperty(value = "转账状态", converter = DictConvert.class) - @DictFormat(DictTypeConstants.TRANSFER_STATUS) - @Schema(description = "转账状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "2") - private Integer status; - - @ExcelProperty("转账成功时间") - @Schema(description = "转账成功时间") - private LocalDateTime successTime; - - @Schema(description = "转账金额", requiredMode = Schema.RequiredMode.REQUIRED, example = "964") - @ExcelProperty(value = "转账金额", converter = MoneyConvert.class) - private Integer price; - - @ExcelProperty("转账标题") - @Schema(description = "转账标题", requiredMode = Schema.RequiredMode.REQUIRED, example = "冲冲冲!") - private String subject; - - @Schema(description = "收款人姓名", example = "王五") - @ExcelProperty("收款人姓名") - private String userName; - - @Schema(description = "收款人账号", requiredMode = Schema.RequiredMode.REQUIRED, example = "26589") - @ExcelProperty("收款人账号") - private String userAccount; - - @Schema(description = "异步通知商户地址", requiredMode = Schema.RequiredMode.REQUIRED, example = "https://www.iocoder.cn") - private String notifyUrl; - - @ExcelProperty("用户 IP") - @Schema(description = "用户 IP", requiredMode = Schema.RequiredMode.REQUIRED) - private String userIp; - - @Schema(description = "渠道的额外参数") - private Map channelExtras; - - @Schema(description = "渠道转账单号") - @ExcelProperty("渠道转账单号") - private String channelTransferNo; - - @Schema(description = "调用渠道的错误码") - private String channelErrorCode; - - @ExcelProperty("渠道错误提示") - @Schema(description = "调用渠道的错误提示") - private String channelErrorMsg; - - @Schema(description = "渠道的同步/异步通知的内容") - private String channelNotifyData; - - @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED) - @ExcelProperty("创建时间") - private LocalDateTime createTime; - -} diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/wallet/PayWalletController.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/wallet/PayWalletController.java deleted file mode 100644 index 54fa004..0000000 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/wallet/PayWalletController.java +++ /dev/null @@ -1,70 +0,0 @@ -package cn.iocoder.yudao.module.pay.controller.admin.wallet; - -import cn.iocoder.yudao.framework.common.pojo.CommonResult; -import cn.iocoder.yudao.framework.common.pojo.PageResult; -import cn.iocoder.yudao.module.pay.controller.admin.wallet.vo.wallet.PayWalletPageReqVO; -import cn.iocoder.yudao.module.pay.controller.admin.wallet.vo.wallet.PayWalletRespVO; -import cn.iocoder.yudao.module.pay.controller.admin.wallet.vo.wallet.PayWalletUpdateBalanceReqVO; -import cn.iocoder.yudao.module.pay.controller.admin.wallet.vo.wallet.PayWalletUserReqVO; -import cn.iocoder.yudao.module.pay.convert.wallet.PayWalletConvert; -import cn.iocoder.yudao.module.pay.dal.dataobject.wallet.PayWalletDO; -import cn.iocoder.yudao.module.pay.enums.wallet.PayWalletBizTypeEnum; -import cn.iocoder.yudao.module.pay.service.wallet.PayWalletService; -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.tags.Tag; -import jakarta.annotation.Resource; -import jakarta.validation.Valid; -import lombok.extern.slf4j.Slf4j; -import org.springframework.security.access.prepost.PreAuthorize; -import org.springframework.validation.annotation.Validated; -import org.springframework.web.bind.annotation.*; - -import static cn.iocoder.yudao.framework.common.enums.UserTypeEnum.MEMBER; -import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; -import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; -import static cn.iocoder.yudao.module.pay.enums.ErrorCodeConstants.WALLET_NOT_FOUND; - -@Tag(name = "管理后台 - 用户钱包") -@RestController -@RequestMapping("/pay/wallet") -@Validated -@Slf4j -public class PayWalletController { - - @Resource - private PayWalletService payWalletService; - - @GetMapping("/get") - @PreAuthorize("@ss.hasPermission('pay:wallet:query')") - @Operation(summary = "获得用户钱包明细") - public CommonResult getWallet(PayWalletUserReqVO reqVO) { - PayWalletDO wallet = payWalletService.getOrCreateWallet(reqVO.getUserId(), MEMBER.getValue()); - return success(PayWalletConvert.INSTANCE.convert02(wallet)); - } - - @GetMapping("/page") - @Operation(summary = "获得会员钱包分页") - @PreAuthorize("@ss.hasPermission('pay:wallet:query')") - public CommonResult> getWalletPage(@Valid PayWalletPageReqVO pageVO) { - PageResult pageResult = payWalletService.getWalletPage(pageVO); - return success(PayWalletConvert.INSTANCE.convertPage(pageResult)); - } - - @PutMapping("/update-balance") - @Operation(summary = "更新会员用户余额") - @PreAuthorize("@ss.hasPermission('pay:wallet:update-balance')") - public CommonResult updateWalletBalance(@Valid @RequestBody PayWalletUpdateBalanceReqVO updateReqVO) { - // 获得用户钱包 - PayWalletDO wallet = payWalletService.getOrCreateWallet(updateReqVO.getUserId(), MEMBER.getValue()); - if (wallet == null) { - log.error("[updateWalletBalance],updateReqVO({}) 用户钱包不存在.", updateReqVO); - throw exception(WALLET_NOT_FOUND); - } - - // 更新钱包余额 - payWalletService.addWalletBalance(wallet.getId(), String.valueOf(updateReqVO.getUserId()), - PayWalletBizTypeEnum.UPDATE_BALANCE, updateReqVO.getBalance()); - return success(true); - } - -} diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/wallet/PayWalletRechargeController.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/wallet/PayWalletRechargeController.java deleted file mode 100644 index c5e84aa..0000000 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/wallet/PayWalletRechargeController.java +++ /dev/null @@ -1,59 +0,0 @@ -package cn.iocoder.yudao.module.pay.controller.admin.wallet; - -import cn.iocoder.yudao.framework.common.pojo.CommonResult; -import cn.iocoder.yudao.module.pay.api.notify.dto.PayOrderNotifyReqDTO; -import cn.iocoder.yudao.module.pay.api.notify.dto.PayRefundNotifyReqDTO; -import cn.iocoder.yudao.module.pay.service.wallet.PayWalletRechargeService; -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.Parameter; -import io.swagger.v3.oas.annotations.tags.Tag; -import lombok.extern.slf4j.Slf4j; -import org.springframework.validation.annotation.Validated; -import org.springframework.web.bind.annotation.*; - -import jakarta.annotation.Resource; -import jakarta.annotation.security.PermitAll; -import jakarta.validation.Valid; - -import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; -import static cn.iocoder.yudao.framework.common.util.servlet.ServletUtils.getClientIP; - -@Tag(name = "管理后台 - 钱包充值") -@RestController -@RequestMapping("/pay/wallet-recharge") -@Validated -@Slf4j -public class PayWalletRechargeController { - - @Resource - private PayWalletRechargeService walletRechargeService; - - @PostMapping("/update-paid") - @Operation(summary = "更新钱包充值为已充值") // 由 pay-module 支付服务,进行回调,可见 PayNotifyJob - @PermitAll // 无需登录, 内部校验实现 - public CommonResult updateWalletRechargerPaid(@Valid @RequestBody PayOrderNotifyReqDTO notifyReqDTO) { - walletRechargeService.updateWalletRechargerPaid(Long.valueOf(notifyReqDTO.getMerchantOrderId()), - notifyReqDTO.getPayOrderId()); - return success(true); - } - - @PostMapping("/refund") - @Operation(summary = "发起钱包充值退款") - @Parameter(name = "id", description = "编号", required = true, example = "1024") - public CommonResult refundWalletRecharge(@RequestParam("id") Long id) { - walletRechargeService.refundWalletRecharge(id, getClientIP()); - return success(true); - } - - @PostMapping("/update-refunded") - @Operation(summary = "更新钱包充值为已退款") // 由 pay-module 支付服务,进行回调,可见 PayNotifyJob - @PermitAll // 无需登录, 内部校验实现 - public CommonResult updateWalletRechargeRefunded(@RequestBody PayRefundNotifyReqDTO notifyReqDTO) { - walletRechargeService.updateWalletRechargeRefunded( - Long.valueOf(notifyReqDTO.getMerchantOrderId()), - Long.valueOf(notifyReqDTO.getMerchantRefundId()), - notifyReqDTO.getPayRefundId()); - return success(true); - } - -} diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/wallet/PayWalletRechargePackageController.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/wallet/PayWalletRechargePackageController.java deleted file mode 100644 index eb8675b..0000000 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/wallet/PayWalletRechargePackageController.java +++ /dev/null @@ -1,75 +0,0 @@ -package cn.iocoder.yudao.module.pay.controller.admin.wallet; - -import cn.iocoder.yudao.framework.common.pojo.CommonResult; -import cn.iocoder.yudao.framework.common.pojo.PageResult; -import cn.iocoder.yudao.module.pay.controller.admin.wallet.vo.rechargepackage.WalletRechargePackageCreateReqVO; -import cn.iocoder.yudao.module.pay.controller.admin.wallet.vo.rechargepackage.WalletRechargePackagePageReqVO; -import cn.iocoder.yudao.module.pay.controller.admin.wallet.vo.rechargepackage.WalletRechargePackageRespVO; -import cn.iocoder.yudao.module.pay.controller.admin.wallet.vo.rechargepackage.WalletRechargePackageUpdateReqVO; -import cn.iocoder.yudao.module.pay.convert.wallet.PayWalletRechargePackageConvert; -import cn.iocoder.yudao.module.pay.dal.dataobject.wallet.PayWalletRechargePackageDO; -import cn.iocoder.yudao.module.pay.service.wallet.PayWalletRechargePackageService; -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.Parameter; -import io.swagger.v3.oas.annotations.tags.Tag; -import org.springframework.security.access.prepost.PreAuthorize; -import org.springframework.validation.annotation.Validated; -import org.springframework.web.bind.annotation.*; - -import jakarta.annotation.Resource; -import jakarta.validation.Valid; - -import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; - - -@Tag(name = "管理后台 - 钱包充值套餐") -@RestController -@RequestMapping("/pay/wallet-recharge-package") -@Validated -public class PayWalletRechargePackageController { - - @Resource - private PayWalletRechargePackageService walletRechargePackageService; - - @PostMapping("/create") - @Operation(summary = "创建钱包充值套餐") - @PreAuthorize("@ss.hasPermission('pay:wallet-recharge-package:create')") - public CommonResult createWalletRechargePackage(@Valid @RequestBody WalletRechargePackageCreateReqVO createReqVO) { - return success(walletRechargePackageService.createWalletRechargePackage(createReqVO)); - } - - @PutMapping("/update") - @Operation(summary = "更新钱包充值套餐") - @PreAuthorize("@ss.hasPermission('pay:wallet-recharge-package:update')") - public CommonResult updateWalletRechargePackage(@Valid @RequestBody WalletRechargePackageUpdateReqVO updateReqVO) { - walletRechargePackageService.updateWalletRechargePackage(updateReqVO); - return success(true); - } - - @DeleteMapping("/delete") - @Operation(summary = "删除钱包充值套餐") - @Parameter(name = "id", description = "编号", required = true) - @PreAuthorize("@ss.hasPermission('pay:wallet-recharge-package:delete')") - public CommonResult deleteWalletRechargePackage(@RequestParam("id") Long id) { - walletRechargePackageService.deleteWalletRechargePackage(id); - return success(true); - } - - @GetMapping("/get") - @Operation(summary = "获得钱包充值套餐") - @Parameter(name = "id", description = "编号", required = true, example = "1024") - @PreAuthorize("@ss.hasPermission('pay:wallet-recharge-package:query')") - public CommonResult getWalletRechargePackage(@RequestParam("id") Long id) { - PayWalletRechargePackageDO walletRechargePackage = walletRechargePackageService.getWalletRechargePackage(id); - return success(PayWalletRechargePackageConvert.INSTANCE.convert(walletRechargePackage)); - } - - @GetMapping("/page") - @Operation(summary = "获得钱包充值套餐分页") - @PreAuthorize("@ss.hasPermission('pay:wallet-recharge-package:query')") - public CommonResult> getWalletRechargePackagePage(@Valid WalletRechargePackagePageReqVO pageVO) { - PageResult pageResult = walletRechargePackageService.getWalletRechargePackagePage(pageVO); - return success(PayWalletRechargePackageConvert.INSTANCE.convertPage(pageResult)); - } - -} diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/wallet/PayWalletTransactionController.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/wallet/PayWalletTransactionController.java deleted file mode 100644 index 4b9aee6..0000000 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/wallet/PayWalletTransactionController.java +++ /dev/null @@ -1,43 +0,0 @@ -package cn.iocoder.yudao.module.pay.controller.admin.wallet; - -import cn.iocoder.yudao.framework.common.pojo.CommonResult; -import cn.iocoder.yudao.framework.common.pojo.PageResult; -import cn.iocoder.yudao.module.pay.controller.admin.wallet.vo.transaction.PayWalletTransactionPageReqVO; -import cn.iocoder.yudao.module.pay.controller.admin.wallet.vo.transaction.PayWalletTransactionRespVO; -import cn.iocoder.yudao.module.pay.convert.wallet.PayWalletTransactionConvert; -import cn.iocoder.yudao.module.pay.dal.dataobject.wallet.PayWalletTransactionDO; -import cn.iocoder.yudao.module.pay.service.wallet.PayWalletTransactionService; -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.tags.Tag; -import lombok.extern.slf4j.Slf4j; -import org.springframework.security.access.prepost.PreAuthorize; -import org.springframework.validation.annotation.Validated; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; - -import jakarta.annotation.Resource; -import jakarta.validation.Valid; - -import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; - -@Tag(name = "管理后台 - 钱包余额明细") -@RestController -@RequestMapping("/pay/wallet-transaction") -@Validated -@Slf4j -public class PayWalletTransactionController { - - @Resource - private PayWalletTransactionService payWalletTransactionService; - - @GetMapping("/page") - @Operation(summary = "获得钱包流水分页") - @PreAuthorize("@ss.hasPermission('pay:wallet:query')") - public CommonResult> getWalletTransactionPage( - @Valid PayWalletTransactionPageReqVO pageReqVO) { - PageResult result = payWalletTransactionService.getWalletTransactionPage(pageReqVO); - return success(PayWalletTransactionConvert.INSTANCE.convertPage2(result)); - } - -} diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/wallet/vo/rechargepackage/WalletRechargePackageBaseVO.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/wallet/vo/rechargepackage/WalletRechargePackageBaseVO.java deleted file mode 100644 index ea6f0c4..0000000 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/wallet/vo/rechargepackage/WalletRechargePackageBaseVO.java +++ /dev/null @@ -1,31 +0,0 @@ -package cn.iocoder.yudao.module.pay.controller.admin.wallet.vo.rechargepackage; - -import io.swagger.v3.oas.annotations.media.Schema; -import lombok.Data; - -import jakarta.validation.constraints.NotNull; - -/** - * 充值套餐 Base VO,提供给添加、修改、详细的子 VO 使用 - * 如果子 VO 存在差异的字段,请不要添加到这里,影响 Swagger 文档生成 - */ -@Data -public class WalletRechargePackageBaseVO { - - @Schema(description = "套餐名", requiredMode = Schema.RequiredMode.REQUIRED, example = "李四") - @NotNull(message = "套餐名不能为空") - private String name; - - @Schema(description = "支付金额", requiredMode = Schema.RequiredMode.REQUIRED, example = "16454") - @NotNull(message = "支付金额不能为空") - private Integer payPrice; - - @Schema(description = "赠送金额", requiredMode = Schema.RequiredMode.REQUIRED, example = "20887") - @NotNull(message = "赠送金额不能为空") - private Integer bonusPrice; - - @Schema(description = "状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "2") - @NotNull(message = "状态不能为空") - private Byte status; - -} diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/wallet/vo/rechargepackage/WalletRechargePackageCreateReqVO.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/wallet/vo/rechargepackage/WalletRechargePackageCreateReqVO.java deleted file mode 100644 index 4232a99..0000000 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/wallet/vo/rechargepackage/WalletRechargePackageCreateReqVO.java +++ /dev/null @@ -1,14 +0,0 @@ -package cn.iocoder.yudao.module.pay.controller.admin.wallet.vo.rechargepackage; - -import io.swagger.v3.oas.annotations.media.Schema; -import lombok.Data; -import lombok.EqualsAndHashCode; -import lombok.ToString; - -@Schema(description = "管理后台 - 充值套餐创建 Request VO") -@Data -@EqualsAndHashCode(callSuper = true) -@ToString(callSuper = true) -public class WalletRechargePackageCreateReqVO extends WalletRechargePackageBaseVO { - -} diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/wallet/vo/rechargepackage/WalletRechargePackagePageReqVO.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/wallet/vo/rechargepackage/WalletRechargePackagePageReqVO.java deleted file mode 100644 index 346e859..0000000 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/wallet/vo/rechargepackage/WalletRechargePackagePageReqVO.java +++ /dev/null @@ -1,30 +0,0 @@ -package cn.iocoder.yudao.module.pay.controller.admin.wallet.vo.rechargepackage; - -import cn.iocoder.yudao.framework.common.pojo.PageParam; -import io.swagger.v3.oas.annotations.media.Schema; -import lombok.Data; -import lombok.EqualsAndHashCode; -import lombok.ToString; -import org.springframework.format.annotation.DateTimeFormat; - -import java.time.LocalDateTime; - -import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND; - -@Schema(description = "管理后台 - 充值套餐分页 Request VO") -@Data -@EqualsAndHashCode(callSuper = true) -@ToString(callSuper = true) -public class WalletRechargePackagePageReqVO extends PageParam { - - @Schema(description = "套餐名", example = "李四") - private String name; - - @Schema(description = "状态", example = "2") - private Integer status; - - @Schema(description = "创建时间") - @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) - private LocalDateTime[] createTime; - -} diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/wallet/vo/rechargepackage/WalletRechargePackageRespVO.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/wallet/vo/rechargepackage/WalletRechargePackageRespVO.java deleted file mode 100644 index 84abaf7..0000000 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/wallet/vo/rechargepackage/WalletRechargePackageRespVO.java +++ /dev/null @@ -1,19 +0,0 @@ -package cn.iocoder.yudao.module.pay.controller.admin.wallet.vo.rechargepackage; - -import io.swagger.v3.oas.annotations.media.Schema; -import lombok.*; -import java.time.LocalDateTime; - -@Schema(description = "管理后台 - 充值套餐 Response VO") -@Data -@EqualsAndHashCode(callSuper = true) -@ToString(callSuper = true) -public class WalletRechargePackageRespVO extends WalletRechargePackageBaseVO { - - @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "9032") - private Long id; - - @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED) - private LocalDateTime createTime; - -} diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/wallet/vo/rechargepackage/WalletRechargePackageUpdateReqVO.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/wallet/vo/rechargepackage/WalletRechargePackageUpdateReqVO.java deleted file mode 100644 index 77b230f..0000000 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/wallet/vo/rechargepackage/WalletRechargePackageUpdateReqVO.java +++ /dev/null @@ -1,20 +0,0 @@ -package cn.iocoder.yudao.module.pay.controller.admin.wallet.vo.rechargepackage; - -import io.swagger.v3.oas.annotations.media.Schema; -import lombok.Data; -import lombok.EqualsAndHashCode; -import lombok.ToString; - -import jakarta.validation.constraints.NotNull; - -@Schema(description = "管理后台 - 充值套餐更新 Request VO") -@Data -@EqualsAndHashCode(callSuper = true) -@ToString(callSuper = true) -public class WalletRechargePackageUpdateReqVO extends WalletRechargePackageBaseVO { - - @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "9032") - @NotNull(message = "编号不能为空") - private Long id; - -} diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/wallet/vo/transaction/PayWalletTransactionPageReqVO.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/wallet/vo/transaction/PayWalletTransactionPageReqVO.java deleted file mode 100644 index 7491b9e..0000000 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/wallet/vo/transaction/PayWalletTransactionPageReqVO.java +++ /dev/null @@ -1,23 +0,0 @@ -package cn.iocoder.yudao.module.pay.controller.admin.wallet.vo.transaction; - -import cn.iocoder.yudao.framework.common.enums.UserTypeEnum; -import cn.iocoder.yudao.framework.common.pojo.PageParam; -import cn.iocoder.yudao.framework.common.validation.InEnum; -import io.swagger.v3.oas.annotations.media.Schema; -import lombok.Data; - -@Schema(description = "管理后台 - 钱包流水分页 Request VO") -@Data -public class PayWalletTransactionPageReqVO extends PageParam { - - @Schema(description = "钱包编号", example = "888") - private Long walletId; - - @Schema(description = "用户编号", example = "1024") - private Long userId; - - @Schema(description = "用户类型", example = "1") - @InEnum(UserTypeEnum.class) - private Integer userType; - -} diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/wallet/vo/transaction/PayWalletTransactionRespVO.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/wallet/vo/transaction/PayWalletTransactionRespVO.java deleted file mode 100644 index 6203b78..0000000 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/wallet/vo/transaction/PayWalletTransactionRespVO.java +++ /dev/null @@ -1,35 +0,0 @@ -package cn.iocoder.yudao.module.pay.controller.admin.wallet.vo.transaction; - -import io.swagger.v3.oas.annotations.media.Schema; -import lombok.Data; - -import java.time.LocalDateTime; - -@Schema(description = "用户 APP - 钱包流水分页 Response VO") -@Data -public class PayWalletTransactionRespVO { - - @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") - private Long id; - - @Schema(description = "钱包编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "5") - private Long walletId; - - @Schema(description = "业务分类", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") - private Integer bizType; - - @Schema(description = "交易金额,单位分", requiredMode = Schema.RequiredMode.REQUIRED, example = "100") - private Long price; - - @Schema(description = "流水标题", requiredMode = Schema.RequiredMode.REQUIRED, example = "土豆土豆") - private String title; - - @Schema(description = "交易后的余额,单位分", requiredMode = Schema.RequiredMode.REQUIRED, example = "100") - private Long balance; - - @Schema(description = "交易时间", requiredMode = Schema.RequiredMode.REQUIRED) - private LocalDateTime createTime; - - // TODO @jason:merchantOrderId 字段,需要在 PayWalletTransaction 存储下;然后,前端也返回下这个字段,界面也展示下商户名 - -} diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/wallet/vo/wallet/PayWalletBaseVO.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/wallet/vo/wallet/PayWalletBaseVO.java deleted file mode 100644 index 2e319de..0000000 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/wallet/vo/wallet/PayWalletBaseVO.java +++ /dev/null @@ -1,39 +0,0 @@ -package cn.iocoder.yudao.module.pay.controller.admin.wallet.vo.wallet; - -import io.swagger.v3.oas.annotations.media.Schema; -import lombok.Data; - -import jakarta.validation.constraints.NotNull; - -/** - * 用户钱包 Base VO,提供给添加、修改、详细的子 VO 使用 - * 如果子 VO 存在差异的字段,请不要添加到这里,影响 Swagger 文档生成 - */ -@Data -public class PayWalletBaseVO { - - @Schema(description = "用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "20020") - @NotNull(message = "用户编号不能为空") - private Long userId; - - @Schema(description = "用户类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") - @NotNull(message = "用户类型不能为空") - private Integer userType; - - @Schema(description = "余额,单位分", requiredMode = Schema.RequiredMode.REQUIRED) - @NotNull(message = "余额,单位分不能为空") - private Integer balance; - - @Schema(description = "累计支出,单位分", requiredMode = Schema.RequiredMode.REQUIRED) - @NotNull(message = "累计支出,单位分不能为空") - private Integer totalExpense; - - @Schema(description = "累计充值,单位分", requiredMode = Schema.RequiredMode.REQUIRED) - @NotNull(message = "累计充值,单位分不能为空") - private Integer totalRecharge; - - @Schema(description = "冻结金额,单位分", requiredMode = Schema.RequiredMode.REQUIRED, example = "20737") - @NotNull(message = "冻结金额,单位分不能为空") - private Integer freezePrice; - -} \ No newline at end of file diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/wallet/vo/wallet/PayWalletPageReqVO.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/wallet/vo/wallet/PayWalletPageReqVO.java deleted file mode 100644 index a316982..0000000 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/wallet/vo/wallet/PayWalletPageReqVO.java +++ /dev/null @@ -1,33 +0,0 @@ -package cn.iocoder.yudao.module.pay.controller.admin.wallet.vo.wallet; - -import cn.iocoder.yudao.framework.common.enums.UserTypeEnum; -import cn.iocoder.yudao.framework.common.pojo.PageParam; -import cn.iocoder.yudao.framework.common.validation.InEnum; -import io.swagger.v3.oas.annotations.media.Schema; -import lombok.Data; -import lombok.EqualsAndHashCode; -import lombok.ToString; -import org.springframework.format.annotation.DateTimeFormat; - -import java.time.LocalDateTime; - -import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND; - -@Schema(description = "管理后台 - 会员钱包分页 Request VO") -@Data -@EqualsAndHashCode(callSuper = true) -@ToString(callSuper = true) -public class PayWalletPageReqVO extends PageParam { - - @Schema(description = "用户编号", example = "1024") - private Long userId; - - @Schema(description = "用户类型", example = "1") - @InEnum(value = UserTypeEnum.class) - private Integer userType; - - @Schema(description = "创建时间") - @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) - private LocalDateTime[] createTime; - -} diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/wallet/vo/wallet/PayWalletRespVO.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/wallet/vo/wallet/PayWalletRespVO.java deleted file mode 100644 index 01b35fb..0000000 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/wallet/vo/wallet/PayWalletRespVO.java +++ /dev/null @@ -1,22 +0,0 @@ -package cn.iocoder.yudao.module.pay.controller.admin.wallet.vo.wallet; - -import io.swagger.v3.oas.annotations.media.Schema; -import lombok.Data; -import lombok.EqualsAndHashCode; -import lombok.ToString; - -import java.time.LocalDateTime; - -@Schema(description = "管理后台 - 用户钱包 Response VO") -@Data -@EqualsAndHashCode(callSuper = true) -@ToString(callSuper = true) -public class PayWalletRespVO extends PayWalletBaseVO { - - @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "29528") - private Long id; - - @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED) - private LocalDateTime createTime; - -} diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/wallet/vo/wallet/PayWalletUpdateBalanceReqVO.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/wallet/vo/wallet/PayWalletUpdateBalanceReqVO.java deleted file mode 100644 index 7569bca..0000000 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/wallet/vo/wallet/PayWalletUpdateBalanceReqVO.java +++ /dev/null @@ -1,19 +0,0 @@ -package cn.iocoder.yudao.module.pay.controller.admin.wallet.vo.wallet; - -import io.swagger.v3.oas.annotations.media.Schema; -import jakarta.validation.constraints.NotNull; -import lombok.Data; - -@Schema(description = "管理后台 - 修改钱包余额 Request VO") -@Data -public class PayWalletUpdateBalanceReqVO { - - @Schema(description = "用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "23788") - @NotNull(message = "用户编号不能为空") - private Long userId; - - @Schema(description = "变动余额,正数为增加,负数为减少", requiredMode = Schema.RequiredMode.REQUIRED, example = "100") - @NotNull(message = "变动余额不能为空") - private Integer balance; - -} diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/wallet/vo/wallet/PayWalletUserReqVO.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/wallet/vo/wallet/PayWalletUserReqVO.java deleted file mode 100644 index e6d4986..0000000 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/wallet/vo/wallet/PayWalletUserReqVO.java +++ /dev/null @@ -1,16 +0,0 @@ -package cn.iocoder.yudao.module.pay.controller.admin.wallet.vo.wallet; - -import io.swagger.v3.oas.annotations.media.Schema; -import lombok.Data; - -import jakarta.validation.constraints.NotNull; - -@Schema(description = "管理后台 - 用户钱包明细 Request VO") -@Data -public class PayWalletUserReqVO { - - @Schema(description = "用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") - @NotNull(message = "用户编号不能为空") - private Long userId; - -} diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/app/channel/AppPayChannelController.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/app/channel/AppPayChannelController.java deleted file mode 100644 index 9e9f798..0000000 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/app/channel/AppPayChannelController.java +++ /dev/null @@ -1,39 +0,0 @@ -package cn.iocoder.yudao.module.pay.controller.app.channel; - -import cn.iocoder.yudao.framework.common.pojo.CommonResult; -import cn.iocoder.yudao.module.pay.dal.dataobject.channel.PayChannelDO; -import cn.iocoder.yudao.module.pay.service.channel.PayChannelService; -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.Parameter; -import io.swagger.v3.oas.annotations.tags.Tag; -import jakarta.annotation.Resource; -import org.springframework.validation.annotation.Validated; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestParam; -import org.springframework.web.bind.annotation.RestController; - -import java.util.List; -import java.util.Set; - -import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; -import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet; - -@Tag(name = "用户 App - 支付渠道") -@RestController -@RequestMapping("/pay/channel") -@Validated -public class AppPayChannelController { - - @Resource - private PayChannelService channelService; - - @GetMapping("/get-enable-code-list") - @Operation(summary = "获得指定应用的开启的支付渠道编码列表") - @Parameter(name = "appId", description = "应用编号", required = true, example = "1") - public CommonResult> getEnableChannelCodeList(@RequestParam("appId") Long appId) { - List channels = channelService.getEnableChannelList(appId); - return success(convertSet(channels, PayChannelDO::getCode)); - } - -} diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/app/order/AppPayOrderController.http b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/app/order/AppPayOrderController.http deleted file mode 100644 index 4fe1898..0000000 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/app/order/AppPayOrderController.http +++ /dev/null @@ -1,63 +0,0 @@ -### /pay/create 提交支付订单【alipay_pc】 -POST {{appApi}}/pay/order/submit -Content-Type: application/json -Authorization: Bearer {{appToken}} -tenant-id: {{appTenantId}} - -{ - "id": 174, - "channelCode": "alipay_pc" -} - -### /pay/create 提交支付订单【wx_bar】 -POST {{appApi}}/pay/order/submit -Content-Type: application/json -Authorization: Bearer {{appToken}} -tenant-id: {{appTenantId}} - -{ - "id": 202, - "channelCode": "wx_bar", - "channelExtras": { - "authCode": "134042110834344848" - } -} - -### /pay/create 提交支付订单【wx_pub】 -POST {{appApi}}/pay/order/submit -Content-Type: application/json -Authorization: Bearer {{appToken}} -tenant-id: {{appTenantId}} - -{ - "id": 202, - "channelCode": "wx_pub", - "channelExtras": { - "openid": "ockUAwIZ-0OeMZl9ogcZ4ILrGba0" - } -} - -### /pay/create 提交支付订单【wx_lite】 -POST {{appApi}}/pay/order/submit -Content-Type: application/json -Authorization: Bearer {{appToken}} -tenant-id: {{appTenantId}} - -{ - "id": 202, - "channelCode": "wx_lite", - "channelExtras": { - "openid": "oLefc4g5GjKWHJjLjMSXB3wX0fD0" - } -} - -### /pay/create 提交支付订单【wx_native】 -POST {{appApi}}/pay/order/submit -Content-Type: application/json -Authorization: Bearer {{appToken}} -tenant-id: {{appTenantId}} - -{ - "id": 202, - "channelCode": "wx_native" -} 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 deleted file mode 100644 index 88eeb09..0000000 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/app/order/AppPayOrderController.java +++ /dev/null @@ -1,81 +0,0 @@ -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.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; -import cn.iocoder.yudao.module.pay.controller.app.order.vo.AppPayOrderSubmitRespVO; -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.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; -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.Parameter; -import io.swagger.v3.oas.annotations.Parameters; -import io.swagger.v3.oas.annotations.tags.Tag; -import jakarta.annotation.Resource; -import lombok.extern.slf4j.Slf4j; -import org.springframework.validation.annotation.Validated; -import org.springframework.web.bind.annotation.*; - -import java.util.Objects; - -import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; -import static cn.iocoder.yudao.framework.common.util.servlet.ServletUtils.getClientIP; -import static cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils.getLoginUserId; -import static cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils.getLoginUserType; - -@Tag(name = "用户 APP - 支付订单") -@RestController -@RequestMapping("/pay/order") -@Validated -@Slf4j -public class AppPayOrderController { - - @Resource - private PayOrderService payOrderService; - @Resource - private PayWalletService payWalletService; - - @GetMapping("/get") - @Operation(summary = "获得支付订单") - @Parameters({ - @Parameter(name = "id", description = "编号", required = true, example = "1024"), - @Parameter(name = "sync", description = "是否同步", example = "true") - }) - public CommonResult getOrder(@RequestParam("id") Long id, - @RequestParam(value = "sync", required = false) Boolean sync) { - PayOrderDO order = payOrderService.getOrder(id); - // sync 仅在等待支付 - if (Boolean.TRUE.equals(sync) && PayOrderStatusEnum.isWaiting(order.getStatus())) { - payOrderService.syncOrderQuietly(order.getId()); - // 重新查询,因为同步后,可能会有变化 - order = payOrderService.getOrder(id); - } - return success(BeanUtils.toBean(order, PayOrderRespVO.class)); - } - - @PostMapping("/submit") - @Operation(summary = "提交支付订单") - public CommonResult submitPayOrder(@RequestBody AppPayOrderSubmitReqVO reqVO) { - // 1. 钱包支付事,需要额外传 user_id 和 user_type - if (Objects.equals(reqVO.getChannelCode(), PayChannelEnum.WALLET.getCode())) { - if (reqVO.getChannelExtras() == null) { - reqVO.setChannelExtras(Maps.newHashMapWithExpectedSize(1)); - } - PayWalletDO wallet = payWalletService.getOrCreateWallet(getLoginUserId(), getLoginUserType()); - reqVO.getChannelExtras().put(WalletPayClient.WALLET_ID_KEY, String.valueOf(wallet.getId())); - } - - // 2. 提交支付 - PayOrderSubmitRespVO respVO = payOrderService.submitOrder(reqVO, getClientIP()); - return success(PayOrderConvert.INSTANCE.convert3(respVO)); - } - -} diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/app/order/vo/AppPayOrderSubmitReqVO.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/app/order/vo/AppPayOrderSubmitReqVO.java deleted file mode 100644 index 0b92d25..0000000 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/app/order/vo/AppPayOrderSubmitReqVO.java +++ /dev/null @@ -1,15 +0,0 @@ -package cn.iocoder.yudao.module.pay.controller.app.order.vo; - -import cn.iocoder.yudao.module.pay.controller.admin.order.vo.PayOrderSubmitReqVO; -import io.swagger.v3.oas.annotations.media.Schema; -import lombok.Data; -import lombok.experimental.Accessors; - -import jakarta.validation.constraints.NotEmpty; -import jakarta.validation.constraints.NotNull; -import java.util.Map; - -@Schema(description = "用户 APP - 支付订单提交 Request VO") -@Data -public class AppPayOrderSubmitReqVO extends PayOrderSubmitReqVO { -} diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/app/order/vo/AppPayOrderSubmitRespVO.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/app/order/vo/AppPayOrderSubmitRespVO.java deleted file mode 100644 index 106535e..0000000 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/app/order/vo/AppPayOrderSubmitRespVO.java +++ /dev/null @@ -1,15 +0,0 @@ -package cn.iocoder.yudao.module.pay.controller.app.order.vo; - -import cn.iocoder.yudao.module.pay.controller.admin.order.vo.PayOrderSubmitRespVO; -import io.swagger.v3.oas.annotations.media.Schema; -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Data; -import lombok.NoArgsConstructor; -import lombok.experimental.Accessors; - -@Schema(description = "用户 APP - 支付订单提交 Response VO") -@Data -public class AppPayOrderSubmitRespVO extends PayOrderSubmitRespVO { - -} diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/app/transfer/AppPayTransferController.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/app/transfer/AppPayTransferController.java deleted file mode 100644 index bf8fc29..0000000 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/app/transfer/AppPayTransferController.java +++ /dev/null @@ -1,36 +0,0 @@ -package cn.iocoder.yudao.module.pay.controller.app.transfer; - -import cn.iocoder.yudao.framework.common.pojo.CommonResult; -import cn.iocoder.yudao.module.pay.service.transfer.PayTransferService; -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.Parameter; -import io.swagger.v3.oas.annotations.tags.Tag; -import jakarta.annotation.Resource; -import lombok.extern.slf4j.Slf4j; -import org.springframework.validation.annotation.Validated; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestParam; -import org.springframework.web.bind.annotation.RestController; - -import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; - -@Tag(name = "用户 APP - 转账单") -@RestController -@RequestMapping("/pay/transfer") -@Validated -@Slf4j -public class AppPayTransferController { - - @Resource - private PayTransferService transferService; - - @GetMapping("/sync") - @Operation(summary = "同步转账单") // 目的:解决微信转账的异步回调可能有延迟的问题 - @Parameter(name = "id", description = "编号", required = true, example = "1024") - public CommonResult syncTransfer(@RequestParam("id") Long id) { - transferService.syncTransfer(id); - return success(true); - } - -} diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/app/wallet/AppPayWalletController.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/app/wallet/AppPayWalletController.java deleted file mode 100644 index 6d47f9a..0000000 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/app/wallet/AppPayWalletController.java +++ /dev/null @@ -1,42 +0,0 @@ -package cn.iocoder.yudao.module.pay.controller.app.wallet; - -import cn.iocoder.yudao.framework.common.enums.UserTypeEnum; -import cn.iocoder.yudao.framework.common.pojo.CommonResult; -import cn.iocoder.yudao.module.pay.controller.app.wallet.vo.wallet.AppPayWalletRespVO; -import cn.iocoder.yudao.module.pay.convert.wallet.PayWalletConvert; -import cn.iocoder.yudao.module.pay.dal.dataobject.wallet.PayWalletDO; -import cn.iocoder.yudao.module.pay.service.wallet.PayWalletService; -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.tags.Tag; -import lombok.extern.slf4j.Slf4j; -import org.springframework.validation.annotation.Validated; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; - -import jakarta.annotation.Resource; - -import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; -import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId; - -/** - * @author jason - */ -@Tag(name = "用户 APP - 钱包") -@RestController -@RequestMapping("/pay/wallet") -@Validated -@Slf4j -public class AppPayWalletController { - - @Resource - private PayWalletService payWalletService; - - @GetMapping("/get") - @Operation(summary = "获取钱包") - public CommonResult getPayWallet() { - PayWalletDO wallet = payWalletService.getOrCreateWallet(getLoginUserId(), UserTypeEnum.MEMBER.getValue()); - return success(PayWalletConvert.INSTANCE.convert(wallet)); - } - -} diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/app/wallet/AppPayWalletRechargeController.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/app/wallet/AppPayWalletRechargeController.java deleted file mode 100644 index 109f2b3..0000000 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/app/wallet/AppPayWalletRechargeController.java +++ /dev/null @@ -1,72 +0,0 @@ -package cn.iocoder.yudao.module.pay.controller.app.wallet; - -import cn.hutool.core.collection.CollUtil; -import cn.iocoder.yudao.framework.common.enums.UserTypeEnum; -import cn.iocoder.yudao.framework.common.pojo.CommonResult; -import cn.iocoder.yudao.framework.common.pojo.PageParam; -import cn.iocoder.yudao.framework.common.pojo.PageResult; -import cn.iocoder.yudao.module.pay.controller.app.wallet.vo.recharge.AppPayWalletRechargeCreateReqVO; -import cn.iocoder.yudao.module.pay.controller.app.wallet.vo.recharge.AppPayWalletRechargeCreateRespVO; -import cn.iocoder.yudao.module.pay.controller.app.wallet.vo.recharge.AppPayWalletRechargeRespVO; -import cn.iocoder.yudao.module.pay.convert.wallet.PayWalletConvert; -import cn.iocoder.yudao.module.pay.convert.wallet.PayWalletRechargeConvert; -import cn.iocoder.yudao.module.pay.dal.dataobject.order.PayOrderDO; -import cn.iocoder.yudao.module.pay.dal.dataobject.wallet.PayWalletRechargeDO; -import cn.iocoder.yudao.module.pay.service.order.PayOrderService; -import cn.iocoder.yudao.module.pay.service.wallet.PayWalletRechargeService; -import com.google.common.collect.Lists; -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.tags.Tag; -import lombok.extern.slf4j.Slf4j; -import org.springframework.validation.annotation.Validated; -import org.springframework.web.bind.annotation.*; - -import jakarta.annotation.Resource; -import jakarta.validation.Valid; - -import java.time.LocalDateTime; -import java.util.Arrays; -import java.util.List; - -import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; -import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList; -import static cn.iocoder.yudao.framework.common.util.servlet.ServletUtils.getClientIP; -import static cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils.getLoginUserId; -import static cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils.getLoginUserType; - -@Tag(name = "用户 APP - 钱包充值") -@RestController -@RequestMapping("/pay/wallet-recharge") -@Validated -@Slf4j -public class AppPayWalletRechargeController { - - @Resource - private PayWalletRechargeService walletRechargeService; - @Resource - private PayOrderService payOrderService; - - @PostMapping("/create") - @Operation(summary = "创建钱包充值记录(发起充值)") - public CommonResult createWalletRecharge( - @Valid @RequestBody AppPayWalletRechargeCreateReqVO reqVO) { - PayWalletRechargeDO walletRecharge = walletRechargeService.createWalletRecharge( - getLoginUserId(), getLoginUserType(), getClientIP(), reqVO); - return success(PayWalletRechargeConvert.INSTANCE.convert(walletRecharge)); - } - - @GetMapping("/page") - @Operation(summary = "获得钱包充值记录分页") - public CommonResult> getWalletRechargePage(@Valid PageParam pageReqVO) { - PageResult pageResult = walletRechargeService.getWalletRechargePackagePage( - getLoginUserId(), UserTypeEnum.MEMBER.getValue(), pageReqVO, true); - if (CollUtil.isEmpty(pageResult.getList())) { - return success(PageResult.empty(pageResult.getTotal())); - } - // 拼接数据 - List payOrderList = payOrderService.getOrderList( - convertList(pageResult.getList(), PayWalletRechargeDO::getPayOrderId)); - return success(PayWalletRechargeConvert.INSTANCE.convertPage(pageResult, payOrderList)); - } - -} diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/app/wallet/AppPayWalletRechargePackageController.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/app/wallet/AppPayWalletRechargePackageController.java deleted file mode 100644 index bab3f86..0000000 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/app/wallet/AppPayWalletRechargePackageController.java +++ /dev/null @@ -1,42 +0,0 @@ -package cn.iocoder.yudao.module.pay.controller.app.wallet; - -import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; -import cn.iocoder.yudao.framework.common.pojo.CommonResult; -import cn.iocoder.yudao.framework.common.util.object.BeanUtils; -import cn.iocoder.yudao.module.pay.controller.app.wallet.vo.recharge.AppPayWalletPackageRespVO; -import cn.iocoder.yudao.module.pay.dal.dataobject.wallet.PayWalletRechargePackageDO; -import cn.iocoder.yudao.module.pay.service.wallet.PayWalletRechargePackageService; -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.tags.Tag; -import jakarta.annotation.Resource; -import lombok.extern.slf4j.Slf4j; -import org.springframework.validation.annotation.Validated; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; - -import java.util.Comparator; -import java.util.List; - -import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; - -@Tag(name = "用户 APP - 钱包充值套餐") -@RestController -@RequestMapping("/pay/wallet-recharge-package") -@Validated -@Slf4j -public class AppPayWalletRechargePackageController { - - @Resource - private PayWalletRechargePackageService walletRechargePackageService; - - @GetMapping("/list") - @Operation(summary = "获得钱包充值套餐列表") - public CommonResult> getWalletRechargePackageList() { - List list = walletRechargePackageService.getWalletRechargePackageList( - CommonStatusEnum.ENABLE.getStatus()); - list.sort(Comparator.comparingInt(PayWalletRechargePackageDO::getPayPrice)); - return success(BeanUtils.toBean(list, AppPayWalletPackageRespVO.class)); - } - -} diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/app/wallet/AppPayWalletTransactionController.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/app/wallet/AppPayWalletTransactionController.java deleted file mode 100644 index 463709e..0000000 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/app/wallet/AppPayWalletTransactionController.java +++ /dev/null @@ -1,61 +0,0 @@ -package cn.iocoder.yudao.module.pay.controller.app.wallet; - -import cn.iocoder.yudao.framework.common.enums.UserTypeEnum; -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.module.pay.controller.app.wallet.vo.transaction.AppPayWalletTransactionSummaryRespVO; -import cn.iocoder.yudao.module.pay.controller.app.wallet.vo.transaction.AppPayWalletTransactionPageReqVO; -import cn.iocoder.yudao.module.pay.controller.app.wallet.vo.transaction.AppPayWalletTransactionRespVO; -import cn.iocoder.yudao.module.pay.dal.dataobject.wallet.PayWalletTransactionDO; -import cn.iocoder.yudao.module.pay.service.wallet.PayWalletTransactionService; -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.Parameter; -import io.swagger.v3.oas.annotations.tags.Tag; -import lombok.extern.slf4j.Slf4j; -import org.springframework.format.annotation.DateTimeFormat; -import org.springframework.validation.annotation.Validated; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestParam; -import org.springframework.web.bind.annotation.RestController; - -import jakarta.annotation.Resource; -import jakarta.validation.Valid; - -import java.time.LocalDateTime; - -import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; -import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND; -import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId; - -@Tag(name = "用户 APP - 钱包余额明细") -@RestController -@RequestMapping("/pay/wallet-transaction") -@Validated -@Slf4j -public class AppPayWalletTransactionController { - - @Resource - private PayWalletTransactionService payWalletTransactionService; - - @GetMapping("/page") - @Operation(summary = "获得钱包流水分页") - public CommonResult> getWalletTransactionPage( - @Valid AppPayWalletTransactionPageReqVO pageReqVO) { - PageResult pageResult = payWalletTransactionService.getWalletTransactionPage( - getLoginUserId(), UserTypeEnum.MEMBER.getValue(), pageReqVO); - return success(BeanUtils.toBean(pageResult, AppPayWalletTransactionRespVO.class)); - } - - @GetMapping("/get-summary") - @Operation(summary = "获得钱包流水统计") - @Parameter(name = "times", description = "时间段", required = true) - public CommonResult getWalletTransactionSummary( - @RequestParam("createTime") @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) LocalDateTime[] createTime) { - AppPayWalletTransactionSummaryRespVO summary = payWalletTransactionService.getWalletTransactionSummary( - getLoginUserId(), UserTypeEnum.MEMBER.getValue(), createTime); - return success(summary); - } - -} diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/app/wallet/vo/recharge/AppPayWalletPackageRespVO.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/app/wallet/vo/recharge/AppPayWalletPackageRespVO.java deleted file mode 100644 index c12db98..0000000 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/app/wallet/vo/recharge/AppPayWalletPackageRespVO.java +++ /dev/null @@ -1,20 +0,0 @@ -package cn.iocoder.yudao.module.pay.controller.app.wallet.vo.recharge; - -import io.swagger.v3.oas.annotations.media.Schema; -import lombok.Data; - -@Schema(description = "用户 APP - 用户充值套餐 Response VO") -@Data -public class AppPayWalletPackageRespVO { - - @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") - private Long id; - @Schema(description = "套餐名", requiredMode = Schema.RequiredMode.REQUIRED, example = "小套餐") - private String name; - - @Schema(description = "支付金额", requiredMode = Schema.RequiredMode.REQUIRED, example = "10") - private Integer payPrice; - @Schema(description = "赠送金额", requiredMode = Schema.RequiredMode.REQUIRED, example = "20") - private Integer bonusPrice; - -} diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/app/wallet/vo/recharge/AppPayWalletRechargeCreateReqVO.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/app/wallet/vo/recharge/AppPayWalletRechargeCreateReqVO.java deleted file mode 100644 index 48a83ec..0000000 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/app/wallet/vo/recharge/AppPayWalletRechargeCreateReqVO.java +++ /dev/null @@ -1,26 +0,0 @@ -package cn.iocoder.yudao.module.pay.controller.app.wallet.vo.recharge; - -import io.swagger.v3.oas.annotations.media.Schema; -import lombok.Data; - -import jakarta.validation.constraints.AssertTrue; -import jakarta.validation.constraints.Min; -import java.util.Objects; - -@Schema(description = "用户 APP - 创建钱包充值 Request VO") -@Data -public class AppPayWalletRechargeCreateReqVO { - - @Schema(description = "支付金额", example = "1000") - @Min(value = 1, message = "支付金额必须大于零") - private Integer payPrice; - - @Schema(description = "充值套餐编号", example = "1024") - private Long packageId; - - @AssertTrue(message = "充值金额和充钱套餐不能同时为空") - public boolean isValidPayPriceAndPackageId() { - return Objects.nonNull(payPrice) || Objects.nonNull(packageId); - } - -} diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/app/wallet/vo/recharge/AppPayWalletRechargeCreateRespVO.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/app/wallet/vo/recharge/AppPayWalletRechargeCreateRespVO.java deleted file mode 100644 index 2c4a96f..0000000 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/app/wallet/vo/recharge/AppPayWalletRechargeCreateRespVO.java +++ /dev/null @@ -1,16 +0,0 @@ -package cn.iocoder.yudao.module.pay.controller.app.wallet.vo.recharge; - -import io.swagger.v3.oas.annotations.media.Schema; -import lombok.Data; - -@Schema(description = "用户 APP - 创建钱包充值 Resp VO") -@Data -public class AppPayWalletRechargeCreateRespVO { - - @Schema(description = "钱包充值编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") - private Long id; - - @Schema(description = "支付订单编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "100") - private Long payOrderId; - -} diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/app/wallet/vo/recharge/AppPayWalletRechargeRespVO.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/app/wallet/vo/recharge/AppPayWalletRechargeRespVO.java deleted file mode 100644 index ee55b40..0000000 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/app/wallet/vo/recharge/AppPayWalletRechargeRespVO.java +++ /dev/null @@ -1,42 +0,0 @@ -package cn.iocoder.yudao.module.pay.controller.app.wallet.vo.recharge; - -import io.swagger.v3.oas.annotations.media.Schema; -import lombok.Data; - -import java.time.LocalDateTime; - -@Schema(description = "用户 APP - 钱包充值记录 Resp VO") -@Data -public class AppPayWalletRechargeRespVO { - - @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") - private Long id; - - @Schema(description = "用户实际到账余额", requiredMode = Schema.RequiredMode.REQUIRED, example = "100") - private Integer totalPrice; - - @Schema(description = "实际支付金额", requiredMode = Schema.RequiredMode.REQUIRED, example = "20") - private Integer payPrice; - - @Schema(description = "钱包赠送金额", requiredMode = Schema.RequiredMode.REQUIRED, example = "80") - private Integer bonusPrice; - - @Schema(description = "支付成功的支付渠道", requiredMode = Schema.RequiredMode.REQUIRED) - private String payChannelCode; - - @Schema(description = "支付渠道名", example = "微信小程序支付") - private String payChannelName; - - @Schema(description = "支付订单编号", requiredMode = Schema.RequiredMode.REQUIRED) - private Long payOrderId; - - @Schema(description = "支付成功的外部订单号", requiredMode = Schema.RequiredMode.REQUIRED) - private String payOrderChannelOrderNo; // 从 PayOrderDO 的 channelOrderNo 字段 - - @Schema(description = "订单支付时间", requiredMode = Schema.RequiredMode.REQUIRED) - private LocalDateTime payTime; - - @Schema(description = "退款状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "0") - private Integer refundStatus; - -} diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/app/wallet/vo/transaction/AppPayWalletTransactionPageReqVO.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/app/wallet/vo/transaction/AppPayWalletTransactionPageReqVO.java deleted file mode 100644 index d5daad0..0000000 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/app/wallet/vo/transaction/AppPayWalletTransactionPageReqVO.java +++ /dev/null @@ -1,31 +0,0 @@ -package cn.iocoder.yudao.module.pay.controller.app.wallet.vo.transaction; - -import cn.iocoder.yudao.framework.common.pojo.PageParam; -import cn.iocoder.yudao.framework.common.util.date.DateUtils; -import io.swagger.v3.oas.annotations.media.Schema; -import lombok.Data; -import org.springframework.format.annotation.DateTimeFormat; - -import java.time.LocalDateTime; - -@Schema(description = "用户 APP - 钱包流水分页 Request VO") -@Data -public class AppPayWalletTransactionPageReqVO extends PageParam { - - /** - * 类型 - 收入 - */ - public static final Integer TYPE_INCOME = 1; - /** - * 类型 - 支出 - */ - public static final Integer TYPE_EXPENSE = 2; - - @Schema(description = "类型", example = "1") - private Integer type; - - @Schema(description = "创建时间") - @DateTimeFormat(pattern = DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) - private LocalDateTime[] createTime; - -} diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/app/wallet/vo/transaction/AppPayWalletTransactionRespVO.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/app/wallet/vo/transaction/AppPayWalletTransactionRespVO.java deleted file mode 100644 index 5c20188..0000000 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/app/wallet/vo/transaction/AppPayWalletTransactionRespVO.java +++ /dev/null @@ -1,24 +0,0 @@ -package cn.iocoder.yudao.module.pay.controller.app.wallet.vo.transaction; - -import io.swagger.v3.oas.annotations.media.Schema; -import lombok.Data; - -import java.time.LocalDateTime; - -@Schema(description = "用户 APP - 钱包流水分页 Response VO") -@Data -public class AppPayWalletTransactionRespVO { - - @Schema(description = "业务分类", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") - private Integer bizType; - - @Schema(description = "交易金额,单位分", requiredMode = Schema.RequiredMode.REQUIRED, example = "100") - private Long price; - - @Schema(description = "流水标题", requiredMode = Schema.RequiredMode.REQUIRED, example = "土豆土豆") - private String title; - - @Schema(description = "交易时间", requiredMode = Schema.RequiredMode.REQUIRED) - private LocalDateTime createTime; - -} diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/app/wallet/vo/transaction/AppPayWalletTransactionSummaryRespVO.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/app/wallet/vo/transaction/AppPayWalletTransactionSummaryRespVO.java deleted file mode 100644 index b1cc587..0000000 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/app/wallet/vo/transaction/AppPayWalletTransactionSummaryRespVO.java +++ /dev/null @@ -1,16 +0,0 @@ -package cn.iocoder.yudao.module.pay.controller.app.wallet.vo.transaction; - -import io.swagger.v3.oas.annotations.media.Schema; -import lombok.Data; - -@Schema(description = "用户 APP - 钱包流水统计 Request VO") -@Data -public class AppPayWalletTransactionSummaryRespVO { - - @Schema(description = "累计支出,单位分", requiredMode = Schema.RequiredMode.REQUIRED, example = "1000") - private Integer totalExpense; - - @Schema(description = "累计收入,单位分", requiredMode = Schema.RequiredMode.REQUIRED, example = "2000") - private Integer totalIncome; - -} diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/app/wallet/vo/wallet/AppPayWalletRespVO.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/app/wallet/vo/wallet/AppPayWalletRespVO.java deleted file mode 100644 index 7e42fc5..0000000 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/app/wallet/vo/wallet/AppPayWalletRespVO.java +++ /dev/null @@ -1,19 +0,0 @@ -package cn.iocoder.yudao.module.pay.controller.app.wallet.vo.wallet; - -import io.swagger.v3.oas.annotations.media.Schema; -import lombok.Data; - -@Schema(description = "用户 APP - 用户钱包 Response VO") -@Data -public class AppPayWalletRespVO { - - @Schema(description = "钱包余额,单位分", requiredMode = Schema.RequiredMode.REQUIRED, example = "100") - private Integer balance; - - @Schema(description = "累计支出,单位分", requiredMode = Schema.RequiredMode.REQUIRED, example = "1000") - private Integer totalExpense; - - @Schema(description = "累计充值,单位分", requiredMode = Schema.RequiredMode.REQUIRED, example = "2000") - private Integer totalRecharge; - -} diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/package-info.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/package-info.java deleted file mode 100644 index 6521227..0000000 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/package-info.java +++ /dev/null @@ -1,6 +0,0 @@ -/** - * 提供 RESTful API 给前端: - * 1. admin 包:提供给管理后台 yudao-ui-admin 前端项目 - * 2. app 包:提供给用户 APP yudao-ui-app 前端项目,它的 Controller 和 VO 都要添加 App 前缀,用于和管理后台进行区分 - */ -package cn.iocoder.yudao.module.pay.controller; diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/convert/app/PayAppConvert.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/convert/app/PayAppConvert.java deleted file mode 100644 index 4853d07..0000000 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/convert/app/PayAppConvert.java +++ /dev/null @@ -1,49 +0,0 @@ -package cn.iocoder.yudao.module.pay.convert.app; - -import cn.iocoder.yudao.framework.common.pojo.PageResult; -import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; -import cn.iocoder.yudao.module.pay.controller.admin.app.vo.PayAppCreateReqVO; -import cn.iocoder.yudao.module.pay.controller.admin.app.vo.PayAppPageItemRespVO; -import cn.iocoder.yudao.module.pay.controller.admin.app.vo.PayAppRespVO; -import cn.iocoder.yudao.module.pay.controller.admin.app.vo.PayAppUpdateReqVO; -import cn.iocoder.yudao.module.pay.dal.dataobject.app.PayAppDO; -import cn.iocoder.yudao.module.pay.dal.dataobject.channel.PayChannelDO; -import org.mapstruct.Mapper; -import org.mapstruct.factory.Mappers; - -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.function.Consumer; - -/** - * 支付应用信息 Convert - * - * @author 芋艿 - */ -@Mapper -public interface PayAppConvert { - - PayAppConvert INSTANCE = Mappers.getMapper(PayAppConvert.class); - - PayAppPageItemRespVO pageConvert (PayAppDO bean); - - PayAppDO convert(PayAppCreateReqVO bean); - - PayAppDO convert(PayAppUpdateReqVO bean); - - PayAppRespVO convert(PayAppDO bean); - - List convertList(List list); - - PageResult convertPage(PageResult page); - - default PageResult convertPage(PageResult pageResult, List channels) { - PageResult voPageResult = convertPage(pageResult); - // 处理 channel 关系 - Map> appIdChannelMap = CollectionUtils.convertMultiMap2(channels, PayChannelDO::getAppId, PayChannelDO::getCode); - voPageResult.getList().forEach(app -> app.setChannelCodes(appIdChannelMap.get(app.getId()))); - return voPageResult; - } - -} diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/convert/channel/PayChannelConvert.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/convert/channel/PayChannelConvert.java deleted file mode 100644 index 5baf598..0000000 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/convert/channel/PayChannelConvert.java +++ /dev/null @@ -1,28 +0,0 @@ -package cn.iocoder.yudao.module.pay.convert.channel; - -import cn.iocoder.yudao.framework.common.pojo.PageResult; -import cn.iocoder.yudao.module.pay.controller.admin.channel.vo.PayChannelCreateReqVO; -import cn.iocoder.yudao.module.pay.controller.admin.channel.vo.PayChannelRespVO; -import cn.iocoder.yudao.module.pay.controller.admin.channel.vo.PayChannelUpdateReqVO; -import cn.iocoder.yudao.module.pay.dal.dataobject.channel.PayChannelDO; -import org.mapstruct.Mapper; -import org.mapstruct.Mapping; -import org.mapstruct.factory.Mappers; - -@Mapper -public interface PayChannelConvert { - - PayChannelConvert INSTANCE = Mappers.getMapper(PayChannelConvert.class); - - @Mapping(target = "config",ignore = true) - PayChannelDO convert(PayChannelCreateReqVO bean); - - @Mapping(target = "config",ignore = true) - PayChannelDO convert(PayChannelUpdateReqVO bean); - - @Mapping(target = "config",expression = "java(cn.iocoder.yudao.framework.common.util.json.JsonUtils.toJsonString(bean.getConfig()))") - PayChannelRespVO convert(PayChannelDO bean); - - PageResult convertPage(PageResult page); - -} 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 deleted file mode 100755 index 8321de3..0000000 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/convert/order/PayOrderConvert.java +++ /dev/null @@ -1,74 +0,0 @@ -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.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.*; -import cn.iocoder.yudao.module.pay.controller.app.order.vo.AppPayOrderSubmitRespVO; -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 org.mapstruct.Mapper; -import org.mapstruct.Mapping; -import org.mapstruct.factory.Mappers; - -import java.util.List; -import java.util.Map; - -/** - * 支付订单 Convert - * - * @author aquan - */ -@Mapper -public interface PayOrderConvert { - - PayOrderConvert INSTANCE = Mappers.getMapper(PayOrderConvert.class); - - PayOrderRespVO convert(PayOrderDO bean); - - PayOrderRespDTO convert2(PayOrderDO order); - - default PayOrderDetailsRespVO convert(PayOrderDO order, PayOrderExtensionDO orderExtension, PayAppDO app) { - PayOrderDetailsRespVO respVO = convertDetail(order); - respVO.setExtension(convert(orderExtension)); - if (app != null) { - respVO.setAppName(app.getName()); - } - return respVO; - } - PayOrderDetailsRespVO convertDetail(PayOrderDO bean); - PayOrderDetailsRespVO.PayOrderExtension convert(PayOrderExtensionDO bean); - - default PageResult convertPage(PageResult page, Map appMap) { - PageResult result = convertPage(page); - result.getList().forEach(order -> MapUtils.findAndThen(appMap, order.getAppId(), app -> order.setAppName(app.getName()))); - return result; - } - PageResult convertPage(PageResult page); - - default List convertList(List list, Map appMap) { - return CollectionUtils.convertList(list, order -> { - PayOrderExcelVO excelVO = convertExcel(order); - MapUtils.findAndThen(appMap, order.getAppId(), app -> excelVO.setAppName(app.getName())); - return excelVO; - }); - } - PayOrderExcelVO convertExcel(PayOrderDO bean); - - PayOrderDO convert(PayOrderCreateReqDTO bean); - - @Mapping(target = "id", ignore = true) - PayOrderExtensionDO convert(PayOrderSubmitReqVO bean, String userIp); - - PayOrderUnifiedReqDTO convert2(PayOrderSubmitReqVO reqVO, String userIp); - - @Mapping(source = "order.status", target = "status") - 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/convert/package-info.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/convert/package-info.java deleted file mode 100644 index df43d5a..0000000 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/convert/package-info.java +++ /dev/null @@ -1,6 +0,0 @@ -/** - * 提供 POJO 类的实体转换 - * - * 目前使用 MapStruct 框架 - */ -package cn.iocoder.yudao.module.pay.convert; diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/convert/refund/PayRefundConvert.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/convert/refund/PayRefundConvert.java deleted file mode 100755 index c94aa00..0000000 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/convert/refund/PayRefundConvert.java +++ /dev/null @@ -1,53 +0,0 @@ -package cn.iocoder.yudao.module.pay.convert.refund; - -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.module.pay.api.refund.dto.PayRefundCreateReqDTO; -import cn.iocoder.yudao.module.pay.controller.admin.refund.vo.PayRefundDetailsRespVO; -import cn.iocoder.yudao.module.pay.controller.admin.refund.vo.PayRefundExcelVO; -import cn.iocoder.yudao.module.pay.controller.admin.refund.vo.PayRefundPageItemRespVO; -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.refund.PayRefundDO; -import org.mapstruct.Mapper; -import org.mapstruct.factory.Mappers; - -import java.util.List; -import java.util.Map; - -@Mapper -public interface PayRefundConvert { - - PayRefundConvert INSTANCE = Mappers.getMapper(PayRefundConvert.class); - - - default PayRefundDetailsRespVO convert(PayRefundDO refund, PayAppDO app) { - PayRefundDetailsRespVO respVO = convert(refund); - if (app != null) { - respVO.setAppName(app.getName()); - } - return respVO; - } - PayRefundDetailsRespVO convert(PayRefundDO bean); - PayRefundDetailsRespVO.Order convert(PayOrderDO bean); - - default PageResult convertPage(PageResult page, Map appMap) { - PageResult result = convertPage(page); - result.getList().forEach(order -> MapUtils.findAndThen(appMap, order.getAppId(), app -> order.setAppName(app.getName()))); - return result; - } - PageResult convertPage(PageResult page); - - PayRefundDO convert(PayRefundCreateReqDTO bean); - - default List convertList(List list, Map appMap) { - return CollectionUtils.convertList(list, order -> { - PayRefundExcelVO excelVO = convertExcel(order); - MapUtils.findAndThen(appMap, order.getAppId(), app -> excelVO.setAppName(app.getName())); - return excelVO; - }); - } - PayRefundExcelVO convertExcel(PayRefundDO bean); - -} diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/convert/wallet/PayWalletConvert.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/convert/wallet/PayWalletConvert.java deleted file mode 100644 index 3617f23..0000000 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/convert/wallet/PayWalletConvert.java +++ /dev/null @@ -1,21 +0,0 @@ -package cn.iocoder.yudao.module.pay.convert.wallet; - -import cn.iocoder.yudao.framework.common.pojo.PageResult; -import cn.iocoder.yudao.module.pay.controller.admin.wallet.vo.wallet.PayWalletRespVO; -import cn.iocoder.yudao.module.pay.controller.app.wallet.vo.wallet.AppPayWalletRespVO; -import cn.iocoder.yudao.module.pay.dal.dataobject.wallet.PayWalletDO; -import org.mapstruct.Mapper; -import org.mapstruct.factory.Mappers; - -@Mapper -public interface PayWalletConvert { - - PayWalletConvert INSTANCE = Mappers.getMapper(PayWalletConvert.class); - - AppPayWalletRespVO convert(PayWalletDO bean); - - PayWalletRespVO convert02(PayWalletDO bean); - - PageResult convertPage(PageResult page); - -} diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/convert/wallet/PayWalletRechargeConvert.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/convert/wallet/PayWalletRechargeConvert.java deleted file mode 100644 index 2dda1d1..0000000 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/convert/wallet/PayWalletRechargeConvert.java +++ /dev/null @@ -1,43 +0,0 @@ -package cn.iocoder.yudao.module.pay.convert.wallet; - -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.common.util.object.BeanUtils; -import cn.iocoder.yudao.framework.dict.core.DictFrameworkUtils; -import cn.iocoder.yudao.module.pay.controller.app.wallet.vo.recharge.AppPayWalletRechargeCreateRespVO; -import cn.iocoder.yudao.module.pay.controller.app.wallet.vo.recharge.AppPayWalletRechargeRespVO; -import cn.iocoder.yudao.module.pay.dal.dataobject.order.PayOrderDO; -import cn.iocoder.yudao.module.pay.dal.dataobject.wallet.PayWalletRechargeDO; -import cn.iocoder.yudao.module.pay.enums.DictTypeConstants; -import org.mapstruct.Mapper; -import org.mapstruct.Mapping; -import org.mapstruct.factory.Mappers; - -import java.util.List; -import java.util.Map; - -@Mapper -public interface PayWalletRechargeConvert { - - PayWalletRechargeConvert INSTANCE = Mappers.getMapper(PayWalletRechargeConvert.class); - - @Mapping(target = "totalPrice", expression = "java( payPrice + bonusPrice)") - PayWalletRechargeDO convert(Long walletId, Integer payPrice, Integer bonusPrice, Long packageId); - - AppPayWalletRechargeCreateRespVO convert(PayWalletRechargeDO bean); - - default PageResult convertPage(PageResult pageResult, - List payOrderList) { - PageResult voPageResult = BeanUtils.toBean(pageResult, AppPayWalletRechargeRespVO.class); - Map payOrderMap = CollectionUtils.convertMap(payOrderList, PayOrderDO::getId); - voPageResult.getList().forEach(recharge -> { - recharge.setPayChannelName(DictFrameworkUtils.parseDictDataLabel( - DictTypeConstants.CHANNEL_CODE, recharge.getPayChannelCode())); - MapUtils.findAndThen(payOrderMap, recharge.getPayOrderId(), - order -> recharge.setPayOrderChannelOrderNo(order.getChannelOrderNo())); - }); - return voPageResult; - } - -} diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/convert/wallet/PayWalletRechargePackageConvert.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/convert/wallet/PayWalletRechargePackageConvert.java deleted file mode 100644 index d3af67b..0000000 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/convert/wallet/PayWalletRechargePackageConvert.java +++ /dev/null @@ -1,29 +0,0 @@ -package cn.iocoder.yudao.module.pay.convert.wallet; - -import java.util.*; - -import cn.iocoder.yudao.framework.common.pojo.PageResult; - -import cn.iocoder.yudao.module.pay.controller.admin.wallet.vo.rechargepackage.WalletRechargePackageCreateReqVO; -import cn.iocoder.yudao.module.pay.controller.admin.wallet.vo.rechargepackage.WalletRechargePackageRespVO; -import cn.iocoder.yudao.module.pay.controller.admin.wallet.vo.rechargepackage.WalletRechargePackageUpdateReqVO; -import cn.iocoder.yudao.module.pay.dal.dataobject.wallet.PayWalletRechargePackageDO; -import org.mapstruct.Mapper; -import org.mapstruct.factory.Mappers; - -@Mapper -public interface PayWalletRechargePackageConvert { - - PayWalletRechargePackageConvert INSTANCE = Mappers.getMapper(PayWalletRechargePackageConvert.class); - - PayWalletRechargePackageDO convert(WalletRechargePackageCreateReqVO bean); - - PayWalletRechargePackageDO convert(WalletRechargePackageUpdateReqVO bean); - - WalletRechargePackageRespVO convert(PayWalletRechargePackageDO bean); - - List convertList(List list); - - PageResult convertPage(PageResult page); - -} diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/convert/wallet/PayWalletTransactionConvert.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/convert/wallet/PayWalletTransactionConvert.java deleted file mode 100644 index a64c217..0000000 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/convert/wallet/PayWalletTransactionConvert.java +++ /dev/null @@ -1,20 +0,0 @@ -package cn.iocoder.yudao.module.pay.convert.wallet; - -import cn.iocoder.yudao.framework.common.pojo.PageResult; -import cn.iocoder.yudao.module.pay.controller.admin.wallet.vo.transaction.PayWalletTransactionRespVO; -import cn.iocoder.yudao.module.pay.controller.app.wallet.vo.transaction.AppPayWalletTransactionRespVO; -import cn.iocoder.yudao.module.pay.dal.dataobject.wallet.PayWalletTransactionDO; -import cn.iocoder.yudao.module.pay.service.wallet.bo.WalletTransactionCreateReqBO; -import org.mapstruct.Mapper; -import org.mapstruct.factory.Mappers; - -@Mapper -public interface PayWalletTransactionConvert { - - PayWalletTransactionConvert INSTANCE = Mappers.getMapper(PayWalletTransactionConvert.class); - - PageResult convertPage2(PageResult page); - - PayWalletTransactionDO convert(WalletTransactionCreateReqBO bean); - -} diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/convert/《芋道 Spring Boot 对象转换 MapStruct 入门》.md b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/convert/《芋道 Spring Boot 对象转换 MapStruct 入门》.md deleted file mode 100644 index 8153487..0000000 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/convert/《芋道 Spring Boot 对象转换 MapStruct 入门》.md +++ /dev/null @@ -1 +0,0 @@ - diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/dataobject/app/PayAppDO.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/dataobject/app/PayAppDO.java deleted file mode 100644 index 456a40a..0000000 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/dataobject/app/PayAppDO.java +++ /dev/null @@ -1,66 +0,0 @@ -package cn.iocoder.yudao.module.pay.dal.dataobject.app; - -import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; -import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; -import com.baomidou.mybatisplus.annotation.KeySequence; -import com.baomidou.mybatisplus.annotation.TableId; -import com.baomidou.mybatisplus.annotation.TableName; -import lombok.*; - -/** - * 支付应用 DO - * 一个商户下,可能会有多个支付应用。例如说,京东有京东商城、京东到家等等 - * 不过一般来说,一个商户,只有一个应用哈~ - * - * 即 PayMerchantDO : PayAppDO = 1 : n - * - * @author 芋道源码 - */ -@TableName("pay_app") -@KeySequence("pay_app_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 -@Data -@EqualsAndHashCode(callSuper = true) -@ToString(callSuper = true) -@Builder -@NoArgsConstructor -@AllArgsConstructor -public class PayAppDO extends BaseDO { - - /** - * 应用编号,数据库自增 - */ - @TableId - private Long id; - /** - * 应用标识 - */ - private String appKey; - /** - * 应用名 - */ - private String name; - /** - * 状态 - * - * 枚举 {@link CommonStatusEnum} - */ - private Integer status; - /** - * 备注 - */ - private String remark; - /** - * 支付结果的回调地址 - */ - private String orderNotifyUrl; - /** - * 退款结果的回调地址 - */ - private String refundNotifyUrl; - - /** - * 转账结果的回调地址 - */ - private String transferNotifyUrl; - -} 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 deleted file mode 100644 index 0039786..0000000 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/dataobject/channel/PayChannelDO.java +++ /dev/null @@ -1,118 +0,0 @@ -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.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 - * 一个应用下,会有多种支付渠道,例如说微信支付、支付宝支付等等 - * - * 即 PayAppDO : PayChannelDO = 1 : n - * - * @author 芋道源码 - */ -@TableName(value = "pay_channel", autoResultMap = true) -@KeySequence("pay_channel_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 -@Data -@EqualsAndHashCode(callSuper = true) -@ToString(callSuper = true) -@Builder -@NoArgsConstructor -@AllArgsConstructor -public class PayChannelDO extends TenantBaseDO { - - /** - * 渠道编号,数据库自增 - */ - private Long id; - /** - * 渠道编码 - * - * 枚举 {@link PayChannelEnum} - */ - private String code; - /** - * 状态 - * - * 枚举 {@link CommonStatusEnum} - */ - private Integer status; - /** - * 渠道费率,单位:百分比 - */ - private Double feeRate; - /** - * 备注 - */ - private String remark; - - /** - * 应用编号 - * - * 关联 {@link PayAppDO#getId()} - */ - private Long appId; - /** - * 支付渠道配置 - */ - @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/demo/PayDemoOrderDO.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/dataobject/demo/PayDemoOrderDO.java deleted file mode 100644 index a22f7c1..0000000 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/dataobject/demo/PayDemoOrderDO.java +++ /dev/null @@ -1,87 +0,0 @@ -package cn.iocoder.yudao.module.pay.dal.dataobject.demo; - -import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; -import com.baomidou.mybatisplus.annotation.KeySequence; -import com.baomidou.mybatisplus.annotation.TableId; -import com.baomidou.mybatisplus.annotation.TableName; -import lombok.*; - -import java.time.LocalDateTime; - -/** - * 示例订单 - * - * 演示业务系统的订单,如何接入 pay 系统的支付与退款 - * - * @author 芋道源码 - */ -@TableName("pay_demo_order") -@KeySequence("pay_demo_order_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 -@Data -@EqualsAndHashCode(callSuper = true) -@ToString(callSuper = true) -@Builder -@NoArgsConstructor -@AllArgsConstructor -public class PayDemoOrderDO extends BaseDO { - - /** - * 订单编号,自增 - */ - @TableId - private Long id; - /** - * 用户编号 - */ - private Long userId; - /** - * 商品编号 - */ - private Long spuId; - /** - * 商品名称 - */ - private String spuName; - /** - * 价格,单位:分 - */ - private Integer price; - - // ========== 支付相关字段 ========== - - /** - * 是否支付 - */ - private Boolean payStatus; - /** - * 支付订单编号 - * - * 对接 pay-module-biz 支付服务的支付订单编号,即 PayOrderDO 的 id 编号 - */ - private Long payOrderId; - /** - * 付款时间 - */ - private LocalDateTime payTime; - /** - * 支付渠道 - * - * 对应 PayChannelEnum 枚举 - */ - private String payChannelCode; - - // ========== 退款相关字段 ========== - /** - * 支付退款单号 - */ - private Long payRefundId; - /** - * 退款金额,单位:分 - */ - private Integer refundPrice; - /** - * 退款完成时间 - */ - private LocalDateTime refundTime; - -} diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/dataobject/demo/PayDemoWithdrawDO.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/dataobject/demo/PayDemoWithdrawDO.java deleted file mode 100644 index 71d803c..0000000 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/dataobject/demo/PayDemoWithdrawDO.java +++ /dev/null @@ -1,84 +0,0 @@ -package cn.iocoder.yudao.module.pay.dal.dataobject.demo; - -import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; -import cn.iocoder.yudao.module.pay.dal.dataobject.transfer.PayTransferDO; -import cn.iocoder.yudao.module.pay.enums.demo.PayDemoWithdrawStatusEnum; -import cn.iocoder.yudao.module.pay.enums.demo.PayDemoWithdrawTypeEnum; -import com.baomidou.mybatisplus.annotation.KeySequence; -import com.baomidou.mybatisplus.annotation.TableId; -import com.baomidou.mybatisplus.annotation.TableName; -import lombok.Data; - -import java.time.LocalDateTime; - -/** - * 示例提现订单 - * - * 演示业务系统的转账业务 - */ -@TableName(value ="pay_demo_withdraw", autoResultMap = true) -@KeySequence("pay_demo_withdraw_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 -@Data -public class PayDemoWithdrawDO extends BaseDO { - - /** - * 提现单编号,自增 - */ - @TableId - private Long id; - - /** - * 提现标题 - */ - private String subject; - /** - * 提现金额,单位:分 - */ - private Integer price; - - /** - * 收款人账号 - */ - private String userAccount; - /** - * 收款人姓名 - */ - private String userName; - - /** - * 提现方式 - * - * 枚举 {@link PayDemoWithdrawTypeEnum} - */ - private Integer type; - /** - * 提现状态 - * - * 枚举 {@link PayDemoWithdrawStatusEnum} - */ - private Integer status; - - // ========== 转账相关字段 ========== - - /** - * 转账单编号 - * - * 关联 {@link PayTransferDO#getId()} - */ - private Long payTransferId; - /** - * 转账渠道 - * - * 枚举 {@link cn.iocoder.yudao.module.pay.enums.PayChannelEnum} - */ - private String transferChannelCode; - /** - * 转账成功时间 - */ - private LocalDateTime transferTime; - /** - * 转账错误提示 - */ - private String transferErrorMsg; - -} \ No newline at end of file diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/dataobject/notify/PayNotifyLogDO.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/dataobject/notify/PayNotifyLogDO.java deleted file mode 100644 index 3846283..0000000 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/dataobject/notify/PayNotifyLogDO.java +++ /dev/null @@ -1,51 +0,0 @@ -package cn.iocoder.yudao.module.pay.dal.dataobject.notify; - -import cn.iocoder.yudao.module.pay.enums.notify.PayNotifyStatusEnum; -import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; -import com.baomidou.mybatisplus.annotation.KeySequence; -import com.baomidou.mybatisplus.annotation.TableName; -import lombok.*; - -/** - * 商户支付、退款等的通知 Log - * 每次通知时,都会在该表中,记录一次 Log,方便排查问题 - * - * @author 芋道源码 - */ -@TableName("pay_notify_log") -@KeySequence("pay_notify_log_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 -@Data -@EqualsAndHashCode(callSuper = true) -@Builder -@NoArgsConstructor -@AllArgsConstructor -public class PayNotifyLogDO extends BaseDO { - - /** - * 日志编号,自增 - */ - private Long id; - /** - * 通知任务编号 - * - * 关联 {@link PayNotifyTaskDO#getId()} - */ - private Long taskId; - /** - * 第几次被通知 - * - * 对应到 {@link PayNotifyTaskDO#getNotifyTimes()} - */ - private Integer notifyTimes; - /** - * HTTP 响应结果 - */ - private String response; - /** - * 支付通知状态 - * - * 枚举 {@link PayNotifyStatusEnum} - */ - private Integer status; - -} diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/dataobject/notify/PayNotifyTaskDO.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/dataobject/notify/PayNotifyTaskDO.java deleted file mode 100644 index 92f1c8c..0000000 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/dataobject/notify/PayNotifyTaskDO.java +++ /dev/null @@ -1,106 +0,0 @@ -package cn.iocoder.yudao.module.pay.dal.dataobject.notify; - -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.dal.dataobject.order.PayOrderDO; -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.enums.notify.PayNotifyStatusEnum; -import cn.iocoder.yudao.module.pay.enums.notify.PayNotifyTypeEnum; -import com.baomidou.mybatisplus.annotation.KeySequence; -import com.baomidou.mybatisplus.annotation.TableId; -import com.baomidou.mybatisplus.annotation.TableName; -import lombok.Data; -import lombok.EqualsAndHashCode; -import lombok.experimental.Accessors; - -import java.time.LocalDateTime; - -/** - * 支付通知 - * 在支付系统收到支付渠道的支付、退款的结果后,需要不断的通知到业务系统,直到成功。 - * - * @author 芋道源码 - */ -@TableName("pay_notify_task") -@KeySequence("pay_notify_task_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 -@Data -@EqualsAndHashCode(callSuper = true) -@Accessors(chain = true) -public class PayNotifyTaskDO extends TenantBaseDO { - - /** - * 通知频率,单位为秒。 - * - * 算上首次的通知,实际是一共 1 + 8 = 9 次。 - */ - public static final Integer[] NOTIFY_FREQUENCY = new Integer[]{ - 15, 15, 30, 180, - 1800, 1800, 1800, 3600 - }; - - /** - * 编号,自增 - */ - @TableId - private Long id; - /** - * 应用编号 - * - * 关联 {@link PayAppDO#getId()} - */ - private Long appId; - /** - * 通知类型 - * - * 枚举 {@link PayNotifyTypeEnum} - */ - private Integer type; - /** - * 数据编号,根据不同 type 进行关联: - * - * 1. {@link PayNotifyTypeEnum#ORDER} 时,关联 {@link PayOrderDO#getId()} - * 2. {@link PayNotifyTypeEnum#REFUND} 时,关联 {@link PayRefundDO#getId()} - * 3. {@link PayNotifyTypeEnum#TRANSFER} 时,关联 {@link PayTransferDO#getId()} - */ - private Long dataId; - /** - * 商户订单编号 - */ - private String merchantOrderId; - /** - * 商户退款编号 - */ - private String merchantRefundId; - /** - * 商户转账编号 - */ - private String merchantTransferId; - /** - * 通知状态 - * - * 枚举 {@link PayNotifyStatusEnum} - */ - private Integer status; - /** - * 下一次通知时间 - */ - private LocalDateTime nextNotifyTime; - /** - * 最后一次执行时间 - */ - private LocalDateTime lastExecuteTime; - /** - * 当前通知次数 - */ - private Integer notifyTimes; - /** - * 最大可通知次数 - */ - private Integer maxNotifyTimes; - /** - * 通知地址 - */ - private String notifyUrl; - -} 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 deleted file mode 100644 index ac0ef34..0000000 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/dataobject/order/PayOrderDO.java +++ /dev/null @@ -1,138 +0,0 @@ -package cn.iocoder.yudao.module.pay.dal.dataobject.order; - -import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; -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; -import lombok.*; - -import java.time.LocalDateTime; - -/** - * 支付订单 DO - * - * @author 芋道源码 - */ -@TableName("pay_order") -@KeySequence("pay_order_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 -@Data -@EqualsAndHashCode(callSuper = true) -@ToString(callSuper = true) -@Builder -@NoArgsConstructor -@AllArgsConstructor -public class PayOrderDO extends BaseDO { - - /** - * 订单编号,数据库自增 - */ - private Long id; - /** - * 应用编号 - * - * 关联 {@link PayAppDO#getId()} - */ - private Long appId; - /** - * 渠道编号 - * - * 关联 {@link PayChannelDO#getId()} - */ - private Long channelId; - /** - * 渠道编码 - * - * 枚举 {@link PayChannelEnum} - */ - private String channelCode; - - // ========== 商户相关字段 ========== - - /** - * 商户订单编号 - * - * 例如说,内部系统 A 的订单号,需要保证每个 PayAppDO 唯一 - */ - private String merchantOrderId; - /** - * 商品标题 - */ - private String subject; - /** - * 商品描述信息 - */ - private String body; - /** - * 异步通知地址 - */ - private String notifyUrl; - - // ========== 订单相关字段 ========== - - /** - * 支付金额,单位:分 - */ - private Integer price; - /** - * 渠道手续费,单位:百分比 - * - * 冗余 {@link PayChannelDO#getFeeRate()} - */ - private Double channelFeeRate; - /** - * 渠道手续金额,单位:分 - */ - private Integer channelFeePrice; - /** - * 支付状态 - * - * 枚举 {@link PayOrderStatusEnum} - */ - private Integer status; - /** - * 用户 IP - */ - private String userIp; - /** - * 订单失效时间 - */ - private LocalDateTime expireTime; - /** - * 订单支付成功时间 - */ - private LocalDateTime successTime; - /** - * 支付成功的订单拓展单编号 - * - * 关联 {@link PayOrderExtensionDO#getId()} - */ - private Long extensionId; - /** - * 支付成功的外部订单号 - * - * 关联 {@link PayOrderExtensionDO#getNo()} - */ - private String no; - - // ========== 退款相关字段 ========== - /** - * 退款总金额,单位:分 - */ - private Integer refundPrice; - - // ========== 渠道相关字段 ========== - /** - * 渠道用户编号 - * - * 例如说,微信 openid、支付宝账号 - */ - private String channelUserId; - /** - * 渠道订单号 - */ - private String channelOrderNo; - -} 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 deleted file mode 100644 index 9d2779b..0000000 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/dataobject/order/PayOrderExtensionDO.java +++ /dev/null @@ -1,96 +0,0 @@ -package cn.iocoder.yudao.module.pay.dal.dataobject.order; - -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; -import com.baomidou.mybatisplus.annotation.KeySequence; -import com.baomidou.mybatisplus.annotation.TableField; -import com.baomidou.mybatisplus.annotation.TableName; -import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler; -import lombok.*; - -import java.util.Map; - -/** - * 支付订单拓展 DO - * - * 每次调用支付渠道,都会生成一条对应记录 - * - * @author 芋道源码 - */ -@TableName(value = "pay_order_extension",autoResultMap = true) -@KeySequence("pay_order_extension_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 -@Data -@EqualsAndHashCode(callSuper = true) -@ToString(callSuper = true) -@Builder -@NoArgsConstructor -@AllArgsConstructor -public class PayOrderExtensionDO extends BaseDO { - - /** - * 订单拓展编号,数据库自增 - */ - private Long id; - /** - * 外部订单号,根据规则生成 - * - * 调用支付渠道时,使用该字段作为对接的订单号: - * 1. 微信支付:对应 JSAPI 支付 的 out_trade_no 字段 - * 2. 支付宝支付:对应 电脑网站支付 的 out_trade_no 字段 - * - * 例如说,P202110132239124200055 - */ - private String no; - /** - * 订单号 - * - * 关联 {@link PayOrderDO#getId()} - */ - private Long orderId; - /** - * 渠道编号 - * - * 关联 {@link PayChannelDO#getId()} - */ - private Long channelId; - /** - * 渠道编码 - */ - private String channelCode; - /** - * 用户 IP - */ - private String userIp; - /** - * 支付状态 - * - * 枚举 {@link PayOrderStatusEnum} - */ - private Integer status; - /** - * 支付渠道的额外参数 - * - * 参见 参数说明 - */ - @TableField(typeHandler = JacksonTypeHandler.class) - private Map channelExtras; - - /** - * 调用渠道的错误码 - */ - private String channelErrorCode; - /** - * 调用渠道报错时,错误信息 - */ - private String channelErrorMsg; - - /** - * 支付渠道的同步/异步通知的内容 - * - * 对应 {@link PayOrderRespDTO#getRawData()} - */ - private String channelNotifyData; - -} 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 deleted file mode 100644 index 607483f..0000000 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/dataobject/refund/PayRefundDO.java +++ /dev/null @@ -1,160 +0,0 @@ -package cn.iocoder.yudao.module.pay.dal.dataobject.refund; - -import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; -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; -import cn.iocoder.yudao.module.pay.enums.refund.PayRefundStatusEnum; -import com.baomidou.mybatisplus.annotation.KeySequence; -import com.baomidou.mybatisplus.annotation.TableId; -import com.baomidou.mybatisplus.annotation.TableName; -import lombok.*; - -import java.time.LocalDateTime; - -/** - * 支付退款单 DO - * 一个支付订单,可以拥有多个支付退款单 - * - * 即 PayOrderDO : PayRefundDO = 1 : n - * - * @author 芋道源码 - */ -@TableName("pay_refund") -@KeySequence("pay_refund_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 -@Data -@EqualsAndHashCode(callSuper = true) -@ToString(callSuper = true) -@Builder -@NoArgsConstructor -@AllArgsConstructor -public class PayRefundDO extends BaseDO { - - /** - * 退款单编号,数据库自增 - */ - @TableId - private Long id; - /** - * 外部退款号,根据规则生成 - * - * 调用支付渠道时,使用该字段作为对接的退款号: - * 1. 微信退款:对应 申请退款 的 out_refund_no 字段 - * 2. 支付宝退款:对应 的 out_request_no 字段 - */ - private String no; - - /** - * 应用编号 - * - * 关联 {@link PayAppDO#getId()} - */ - private Long appId; - /** - * 渠道编号 - * - * 关联 {@link PayChannelDO#getId()} - */ - private Long channelId; - /** - * 渠道编码 - * - * 枚举 {@link PayChannelEnum} - */ - private String channelCode; - /** - * 订单编号 - * - * 关联 {@link PayOrderDO#getId()} - */ - private Long orderId; - /** - * 支付订单编号 - * - * 冗余 {@link PayOrderDO#getNo()} - */ - private String orderNo; - - // ========== 商户相关字段 ========== - /** - * 商户订单编号 - * - * 例如说,内部系统 A 的订单号,需要保证每个 PayAppDO 唯一 - */ - private String merchantOrderId; - /** - * 商户退款订单号 - * - * 例如说,内部系统 A 的订单号,需要保证每个 PayAppDO 唯一 - */ - private String merchantRefundId; - /** - * 异步通知地址 - */ - private String notifyUrl; - - // ========== 退款相关字段 ========== - /** - * 退款状态 - * - * 枚举 {@link PayRefundStatusEnum} - */ - private Integer status; - - /** - * 支付金额,单位:分 - */ - private Integer payPrice; - /** - * 退款金额,单位:分 - */ - private Integer refundPrice; - - /** - * 退款原因 - */ - private String reason; - - /** - * 用户 IP - */ - private String userIp; - - // ========== 渠道相关字段 ========== - /** - * 渠道订单号 - * - * 冗余 {@link PayOrderDO#getChannelOrderNo()} - */ - private String channelOrderNo; - /** - * 渠道退款单号 - * - * 1. 微信退款:对应 申请退款 的 refund_id 字段 - * 2. 支付宝退款:没有字段 - */ - private String channelRefundNo; - /** - * 退款成功时间 - */ - private LocalDateTime successTime; - - /** - * 调用渠道的错误码 - */ - private String channelErrorCode; - /** - * 调用渠道的错误提示 - */ - private String channelErrorMsg; - - /** - * 支付渠道的同步/异步通知的内容 - * - * 对应 {@link PayRefundRespDTO#getRawData()} - */ - private String channelNotifyData; - -} 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 deleted file mode 100644 index 94f8521..0000000 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/dataobject/transfer/PayTransferDO.java +++ /dev/null @@ -1,146 +0,0 @@ -package cn.iocoder.yudao.module.pay.dal.dataobject.transfer; - -import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; -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; -import com.baomidou.mybatisplus.annotation.TableName; -import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler; -import lombok.Data; - -import java.time.LocalDateTime; -import java.util.Map; - -/** - * 转账单 DO - * - * @author jason - */ -@TableName(value ="pay_transfer", autoResultMap = true) -@KeySequence("pay_transfer_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 -@Data -public class PayTransferDO extends BaseDO { - - /** - * 编号 - */ - @TableId - private Long id; - - /** - * 转账单号 - */ - private String no; - - /** - * 应用编号 - * - * 关联 {@link PayAppDO#getId()} - */ - private Long appId; - - /** - * 转账渠道编号 - * - * 关联 {@link PayChannelDO#getId()} - */ - private Long channelId; - - /** - * 转账渠道编码 - * - * 枚举 {@link PayChannelEnum} - */ - private String channelCode; - - // ========== 商户相关字段 ========== - /** - * 商户转账单编号 - * - * 例如说,内部系统 A 的订单号,需要保证每个 PayAppDO 唯一 - */ - private String merchantTransferId; - - // ========== 转账相关字段 ========== - - /** - * 转账标题 - */ - private String subject; - - /** - * 转账金额,单位:分 - */ - private Integer price; - - /** - * 收款人账号 - */ - private String userAccount; - /** - * 收款人姓名 - */ - private String userName; - - /** - * 转账状态 - * - * 枚举 {@link PayTransferStatusEnum} - */ - private Integer status; - - /** - * 订单转账成功时间 - */ - private LocalDateTime successTime; - - // ========== 其它字段 ========== - - /** - * 异步通知地址 - */ - private String notifyUrl; - - /** - * 用户 IP - */ - private String userIp; - - /** - * 渠道的额外参数 - */ - @TableField(typeHandler = JacksonTypeHandler.class) - private Map channelExtras; - - /** - * 渠道转账单号 - */ - private String channelTransferNo; - - /** - * 调用渠道的错误码 - */ - private String channelErrorCode; - /** - * 调用渠道的错误提示 - */ - private String channelErrorMsg; - - /** - * 渠道的同步/异步通知的内容 - */ - private String channelNotifyData; - - /** - * 渠道 package 信息 - * - * 特殊:目前只有微信转账有这个东西!!! - * @see JSAPI 调起用户确认收款 - */ - private String channelPackageInfo; - -} \ No newline at end of file diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/dataobject/wallet/PayWalletDO.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/dataobject/wallet/PayWalletDO.java deleted file mode 100644 index a3c54c9..0000000 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/dataobject/wallet/PayWalletDO.java +++ /dev/null @@ -1,59 +0,0 @@ -package cn.iocoder.yudao.module.pay.dal.dataobject.wallet; - -import cn.iocoder.yudao.framework.common.enums.UserTypeEnum; -import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; -import com.baomidou.mybatisplus.annotation.KeySequence; -import com.baomidou.mybatisplus.annotation.TableId; -import com.baomidou.mybatisplus.annotation.TableName; -import lombok.Data; - -/** - * 会员钱包 DO - * - * @author jason - */ -@TableName(value ="pay_wallet") -@KeySequence("pay_wallet_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 -@Data -public class PayWalletDO extends BaseDO { - - /** - * 编号 - */ - @TableId - private Long id; - - /** - * 用户 id - * - * 关联 MemberUserDO 的 id 编号 - * 关联 AdminUserDO 的 id 编号 - */ - private Long userId; - /** - * 用户类型, 预留 多商户转帐可能需要用到 - * - * 关联 {@link UserTypeEnum} - */ - private Integer userType; - - /** - * 余额,单位分 - */ - private Integer balance; - - /** - * 冻结金额,单位分 - */ - private Integer freezePrice; - - /** - * 累计支出,单位分 - */ - private Integer totalExpense; - /** - * 累计充值,单位分 - */ - private Integer totalRecharge; - -} diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/dataobject/wallet/PayWalletRechargeDO.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/dataobject/wallet/PayWalletRechargeDO.java deleted file mode 100644 index a842c95..0000000 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/dataobject/wallet/PayWalletRechargeDO.java +++ /dev/null @@ -1,116 +0,0 @@ -package cn.iocoder.yudao.module.pay.dal.dataobject.wallet; - -import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; -import cn.iocoder.yudao.module.pay.dal.dataobject.order.PayOrderDO; -import cn.iocoder.yudao.module.pay.dal.dataobject.refund.PayRefundDO; -import cn.iocoder.yudao.module.pay.enums.refund.PayRefundStatusEnum; -import com.baomidou.mybatisplus.annotation.KeySequence; -import com.baomidou.mybatisplus.annotation.TableId; -import com.baomidou.mybatisplus.annotation.TableName; -import lombok.Data; - -import java.time.LocalDateTime; - -/** - * 会员钱包充值 - */ -@TableName(value ="pay_wallet_recharge") -@KeySequence("pay_wallet_recharge_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 -@Data -public class PayWalletRechargeDO extends BaseDO { - - /** - * 编号 - */ - @TableId - private Long id; - - /** - * 钱包编号 - * - * 关联 {@link PayWalletDO#getId()} - */ - private Long walletId; - - /** - * 用户实际到账余额 - * - * 例如充 100 送 20,则该值是 120 - */ - private Integer totalPrice; - /** - * 实际支付金额 - */ - private Integer payPrice; - /** - * 钱包赠送金额 - */ - private Integer bonusPrice; - - /** - * 充值套餐编号 - * - * 关联 {@link PayWalletRechargeDO#getPackageId()} 字段 - */ - private Long packageId; - - /** - * 是否已支付 - * - * true - 已支付 - * false - 未支付 - */ - private Boolean payStatus; - - /** - * 支付订单编号 - * - * 关联 {@link PayOrderDO#getId()} - */ - private Long payOrderId; - - /** - * 支付成功的支付渠道 - * - * 冗余 {@link PayOrderDO#getChannelCode()} - */ - private String payChannelCode; - /** - * 订单支付时间 - */ - private LocalDateTime payTime; - - /** - * 支付退款单编号 - * - * 关联 {@link PayRefundDO#getId()} - */ - private Long payRefundId; - - /** - * 退款金额,包含赠送金额 - */ - private Integer refundTotalPrice; - /** - * 退款支付金额 - */ - private Integer refundPayPrice; - - /** - * 退款钱包赠送金额 - */ - private Integer refundBonusPrice; - - /** - * 退款时间 - */ - private LocalDateTime refundTime; - - /** - * 退款状态 - * - * 枚举 {@link PayRefundStatusEnum} - */ - private Integer refundStatus; - -} diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/dataobject/wallet/PayWalletRechargePackageDO.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/dataobject/wallet/PayWalletRechargePackageDO.java deleted file mode 100644 index 72fc43d..0000000 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/dataobject/wallet/PayWalletRechargePackageDO.java +++ /dev/null @@ -1,47 +0,0 @@ -package cn.iocoder.yudao.module.pay.dal.dataobject.wallet; - -import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; -import com.baomidou.mybatisplus.annotation.KeySequence; -import com.baomidou.mybatisplus.annotation.TableId; -import com.baomidou.mybatisplus.annotation.TableName; -import lombok.Data; - -/** - * 会员钱包充值套餐 DO - * - * 通过充值套餐时,可以赠送一定金额; - * - * @author 芋道源码 - */ -@TableName(value ="pay_wallet_recharge_package") -@KeySequence("pay_wallet_recharge_package_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 -@Data -public class PayWalletRechargePackageDO extends BaseDO { - - /** - * 编号 - */ - @TableId - private Long id; - /** - * 套餐名 - */ - private String name; - - /** - * 支付金额 - */ - private Integer payPrice; - /** - * 赠送金额 - */ - private Integer bonusPrice; - - /** - * 状态 - * - * 枚举 {@link cn.iocoder.yudao.framework.common.enums.CommonStatusEnum} - */ - private Integer status; - -} diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/dataobject/wallet/PayWalletTransactionDO.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/dataobject/wallet/PayWalletTransactionDO.java deleted file mode 100644 index 654e511..0000000 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/dataobject/wallet/PayWalletTransactionDO.java +++ /dev/null @@ -1,66 +0,0 @@ -package cn.iocoder.yudao.module.pay.dal.dataobject.wallet; - -import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; -import cn.iocoder.yudao.module.pay.enums.wallet.PayWalletBizTypeEnum; -import com.baomidou.mybatisplus.annotation.KeySequence; -import com.baomidou.mybatisplus.annotation.TableId; -import com.baomidou.mybatisplus.annotation.TableName; -import lombok.Data; - -/** - * 会员钱包流水 DO - * - * @author jason - */ -@TableName(value ="pay_wallet_transaction") -@KeySequence("pay_wallet_transaction_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 -@Data -public class PayWalletTransactionDO extends BaseDO { - - /** - * 编号 - */ - @TableId - private Long id; - - /** - * 流水号 - */ - private String no; - - /** - * 钱包编号 - * - * 关联 {@link PayWalletDO#getId()} - */ - private Long walletId; - - /** - * 关联业务分类 - * - * 枚举 {@link PayWalletBizTypeEnum#getType()} - */ - private Integer bizType; - - /** - * 关联业务编号 - */ - private String bizId; - - /** - * 流水说明 - */ - private String title; - - /** - * 交易金额,单位分 - * - * 正值表示余额增加,负值表示余额减少 - */ - private Integer price; - - /** - * 交易后余额,单位分 - */ - private Integer balance; -} diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/app/PayAppMapper.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/app/PayAppMapper.java deleted file mode 100644 index 07e190a..0000000 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/app/PayAppMapper.java +++ /dev/null @@ -1,26 +0,0 @@ -package cn.iocoder.yudao.module.pay.dal.mysql.app; - -import cn.iocoder.yudao.framework.common.pojo.PageResult; -import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; -import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; -import cn.iocoder.yudao.module.pay.controller.admin.app.vo.PayAppPageReqVO; -import cn.iocoder.yudao.module.pay.dal.dataobject.app.PayAppDO; -import org.apache.ibatis.annotations.Mapper; - -@Mapper -public interface PayAppMapper extends BaseMapperX { - - default PageResult selectPage(PayAppPageReqVO reqVO) { - return selectPage(reqVO, new LambdaQueryWrapperX() - .likeIfPresent(PayAppDO::getName, reqVO.getName()) - .likeIfPresent(PayAppDO::getAppKey, reqVO.getAppKey()) - .eqIfPresent(PayAppDO::getStatus, reqVO.getStatus()) - .betweenIfPresent(PayAppDO::getCreateTime, reqVO.getCreateTime()) - .orderByDesc(PayAppDO::getId)); - } - - default PayAppDO selectByAppKey(String appKey) { - return selectOne(PayAppDO::getAppKey, appKey); - } - -} diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/channel/PayChannelMapper.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/channel/PayChannelMapper.java deleted file mode 100644 index e698498..0000000 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/channel/PayChannelMapper.java +++ /dev/null @@ -1,31 +0,0 @@ -package cn.iocoder.yudao.module.pay.dal.mysql.channel; - -import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; -import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; -import cn.iocoder.yudao.module.pay.dal.dataobject.channel.PayChannelDO; -import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; -import org.apache.ibatis.annotations.Mapper; -import org.apache.ibatis.annotations.Select; - -import java.time.LocalDateTime; -import java.util.Collection; -import java.util.List; - -@Mapper -public interface PayChannelMapper extends BaseMapperX { - - default PayChannelDO selectByAppIdAndCode(Long appId, String code) { - return selectOne(PayChannelDO::getAppId, appId, PayChannelDO::getCode, code); - } - - default List selectListByAppIds(Collection appIds){ - return selectList(PayChannelDO::getAppId, appIds); - } - - default List selectListByAppId(Long appId, Integer status) { - return selectList(new LambdaQueryWrapperX() - .eq(PayChannelDO::getAppId, appId) - .eq(PayChannelDO::getStatus, status)); - } - -} diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/demo/PayDemoOrderMapper.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/demo/PayDemoOrderMapper.java deleted file mode 100644 index 0a92c6b..0000000 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/demo/PayDemoOrderMapper.java +++ /dev/null @@ -1,28 +0,0 @@ -package cn.iocoder.yudao.module.pay.dal.mysql.demo; - -import cn.iocoder.yudao.framework.common.pojo.PageParam; -import cn.iocoder.yudao.framework.common.pojo.PageResult; -import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; -import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; -import cn.iocoder.yudao.module.pay.dal.dataobject.demo.PayDemoOrderDO; -import org.apache.ibatis.annotations.Mapper; - -/** - * 示例订单 Mapper - * - * @author 芋道源码 - */ -@Mapper -public interface PayDemoOrderMapper extends BaseMapperX { - - default PageResult selectPage(PageParam reqVO) { - return selectPage(reqVO, new LambdaQueryWrapperX() - .orderByDesc(PayDemoOrderDO::getId)); - } - - default int updateByIdAndPayed(Long id, boolean wherePayed, PayDemoOrderDO updateObj) { - return update(updateObj, new LambdaQueryWrapperX() - .eq(PayDemoOrderDO::getId, id).eq(PayDemoOrderDO::getPayStatus, wherePayed)); - } - -} diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/demo/PayDemoWithdrawMapper.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/demo/PayDemoWithdrawMapper.java deleted file mode 100644 index 393c161..0000000 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/demo/PayDemoWithdrawMapper.java +++ /dev/null @@ -1,24 +0,0 @@ -package cn.iocoder.yudao.module.pay.dal.mysql.demo; - -import cn.iocoder.yudao.framework.common.pojo.PageParam; -import cn.iocoder.yudao.framework.common.pojo.PageResult; -import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; -import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; -import cn.iocoder.yudao.module.pay.dal.dataobject.demo.PayDemoWithdrawDO; -import org.apache.ibatis.annotations.Mapper; - -@Mapper -public interface PayDemoWithdrawMapper extends BaseMapperX { - - default PageResult selectPage(PageParam pageParam){ - return selectPage(pageParam, new LambdaQueryWrapperX() - .orderByDesc(PayDemoWithdrawDO::getId)); - } - - default int updateByIdAndStatus(Long id, Integer whereStatus, PayDemoWithdrawDO updateObj) { - return update(updateObj, new LambdaQueryWrapperX() - .eq(PayDemoWithdrawDO::getId, id) - .eq(PayDemoWithdrawDO::getStatus, whereStatus)); - } - -} \ No newline at end of file diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/notify/PayNotifyLogMapper.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/notify/PayNotifyLogMapper.java deleted file mode 100644 index 8b586df..0000000 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/notify/PayNotifyLogMapper.java +++ /dev/null @@ -1,16 +0,0 @@ -package cn.iocoder.yudao.module.pay.dal.mysql.notify; - -import cn.iocoder.yudao.module.pay.dal.dataobject.notify.PayNotifyLogDO; -import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; -import org.apache.ibatis.annotations.Mapper; - -import java.util.List; - -@Mapper -public interface PayNotifyLogMapper extends BaseMapperX { - - default List selectListByTaskId(Long taskId) { - return selectList(PayNotifyLogDO::getTaskId, taskId); - } - -} diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/notify/PayNotifyTaskMapper.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/notify/PayNotifyTaskMapper.java deleted file mode 100644 index a3e3eff..0000000 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/notify/PayNotifyTaskMapper.java +++ /dev/null @@ -1,46 +0,0 @@ -package cn.iocoder.yudao.module.pay.dal.mysql.notify; - -import cn.iocoder.yudao.framework.common.pojo.PageResult; -import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; -import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; -import cn.iocoder.yudao.module.pay.controller.admin.notify.vo.PayNotifyTaskPageReqVO; -import cn.iocoder.yudao.module.pay.dal.dataobject.notify.PayNotifyTaskDO; -import cn.iocoder.yudao.module.pay.enums.notify.PayNotifyStatusEnum; -import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; -import org.apache.ibatis.annotations.Mapper; - -import java.time.LocalDateTime; -import java.util.List; - -@Mapper -public interface PayNotifyTaskMapper extends BaseMapperX { - - /** - * 获得需要通知的 PayNotifyTaskDO 记录。需要满足如下条件: - * - * 1. status 非成功 - * 2. nextNotifyTime 小于当前时间 - * - * @return PayTransactionNotifyTaskDO 数组 - */ - default List selectListByNotify() { - return selectList(new LambdaQueryWrapper() - .in(PayNotifyTaskDO::getStatus, PayNotifyStatusEnum.WAITING.getStatus(), - PayNotifyStatusEnum.REQUEST_SUCCESS.getStatus(), PayNotifyStatusEnum.REQUEST_FAILURE.getStatus()) - .le(PayNotifyTaskDO::getNextNotifyTime, LocalDateTime.now())); - } - - default PageResult selectPage(PayNotifyTaskPageReqVO reqVO) { - return selectPage(reqVO, new LambdaQueryWrapperX() - .eqIfPresent(PayNotifyTaskDO::getAppId, reqVO.getAppId()) - .eqIfPresent(PayNotifyTaskDO::getType, reqVO.getType()) - .eqIfPresent(PayNotifyTaskDO::getDataId, reqVO.getDataId()) - .eqIfPresent(PayNotifyTaskDO::getStatus, reqVO.getStatus()) - .eqIfPresent(PayNotifyTaskDO::getMerchantOrderId, reqVO.getMerchantOrderId()) - .eqIfPresent(PayNotifyTaskDO::getMerchantRefundId, reqVO.getMerchantRefundId()) - .eqIfPresent(PayNotifyTaskDO::getMerchantTransferId, reqVO.getMerchantTransferId()) - .betweenIfPresent(PayNotifyTaskDO::getCreateTime, reqVO.getCreateTime()) - .orderByDesc(PayNotifyTaskDO::getId)); - } - -} diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/order/PayOrderExtensionMapper.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/order/PayOrderExtensionMapper.java deleted file mode 100755 index eebcd0e..0000000 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/order/PayOrderExtensionMapper.java +++ /dev/null @@ -1,38 +0,0 @@ -package cn.iocoder.yudao.module.pay.dal.mysql.order; - -import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; -import cn.iocoder.yudao.module.pay.dal.dataobject.order.PayOrderExtensionDO; -import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; -import org.apache.ibatis.annotations.Mapper; - -import java.time.LocalDateTime; -import java.util.List; - -@Mapper -public interface PayOrderExtensionMapper extends BaseMapperX { - - default PayOrderExtensionDO selectByNo(String no) { - return selectOne(PayOrderExtensionDO::getNo, no); - } - - default int updateByIdAndStatus(Long id, Integer status, PayOrderExtensionDO update) { - return update(update, new LambdaQueryWrapper() - .eq(PayOrderExtensionDO::getId, id).eq(PayOrderExtensionDO::getStatus, status)); - } - - default List selectListByOrderId(Long orderId) { - return selectList(PayOrderExtensionDO::getOrderId, orderId); - } - - default List selectListByOrderIdAndStatus(Long orderId, Integer status) { - return selectList(PayOrderExtensionDO::getOrderId, orderId, - PayOrderExtensionDO::getStatus, status); - } - - default List selectListByStatusAndCreateTimeGe(Integer status, LocalDateTime minCreateTime) { - return selectList(new LambdaQueryWrapper() - .eq(PayOrderExtensionDO::getStatus, status) - .ge(PayOrderExtensionDO::getCreateTime, minCreateTime)); - } - -} diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/order/PayOrderMapper.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/order/PayOrderMapper.java deleted file mode 100755 index 95510d5..0000000 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/order/PayOrderMapper.java +++ /dev/null @@ -1,62 +0,0 @@ -package cn.iocoder.yudao.module.pay.dal.mysql.order; - -import cn.iocoder.yudao.framework.common.pojo.PageResult; -import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; -import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; -import cn.iocoder.yudao.module.pay.controller.admin.order.vo.PayOrderExportReqVO; -import cn.iocoder.yudao.module.pay.controller.admin.order.vo.PayOrderPageReqVO; -import cn.iocoder.yudao.module.pay.dal.dataobject.order.PayOrderDO; -import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; -import org.apache.ibatis.annotations.Mapper; - -import java.time.LocalDateTime; -import java.util.List; - -@Mapper -public interface PayOrderMapper extends BaseMapperX { - - default PageResult selectPage(PayOrderPageReqVO reqVO) { - return selectPage(reqVO, new LambdaQueryWrapperX() - .eqIfPresent(PayOrderDO::getAppId, reqVO.getAppId()) - .eqIfPresent(PayOrderDO::getChannelCode, reqVO.getChannelCode()) - .likeIfPresent(PayOrderDO::getMerchantOrderId, reqVO.getMerchantOrderId()) - .likeIfPresent(PayOrderDO::getChannelOrderNo, reqVO.getChannelOrderNo()) - .likeIfPresent(PayOrderDO::getNo, reqVO.getNo()) - .eqIfPresent(PayOrderDO::getStatus, reqVO.getStatus()) - .betweenIfPresent(PayOrderDO::getCreateTime, reqVO.getCreateTime()) - .orderByDesc(PayOrderDO::getId)); - } - - default List selectList(PayOrderExportReqVO reqVO) { - return selectList(new LambdaQueryWrapperX() - .eqIfPresent(PayOrderDO::getAppId, reqVO.getAppId()) - .eqIfPresent(PayOrderDO::getChannelCode, reqVO.getChannelCode()) - .likeIfPresent(PayOrderDO::getMerchantOrderId, reqVO.getMerchantOrderId()) - .likeIfPresent(PayOrderDO::getChannelOrderNo, reqVO.getChannelOrderNo()) - .likeIfPresent(PayOrderDO::getNo, reqVO.getNo()) - .eqIfPresent(PayOrderDO::getStatus, reqVO.getStatus()) - .betweenIfPresent(PayOrderDO::getCreateTime, reqVO.getCreateTime()) - .orderByDesc(PayOrderDO::getId)); - } - - default Long selectCountByAppId(Long appId) { - return selectCount(PayOrderDO::getAppId, appId); - } - - default PayOrderDO selectByAppIdAndMerchantOrderId(Long appId, String merchantOrderId) { - return selectOne(PayOrderDO::getAppId, appId, - PayOrderDO::getMerchantOrderId, merchantOrderId); - } - - default int updateByIdAndStatus(Long id, Integer status, PayOrderDO update) { - return update(update, new LambdaQueryWrapper() - .eq(PayOrderDO::getId, id).eq(PayOrderDO::getStatus, status)); - } - - default List selectListByStatusAndExpireTimeLt(Integer status, LocalDateTime expireTime) { - return selectList(new LambdaQueryWrapper() - .eq(PayOrderDO::getStatus, status) - .lt(PayOrderDO::getExpireTime, expireTime)); - } - -} diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/refund/PayRefundMapper.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/refund/PayRefundMapper.java deleted file mode 100755 index 0b620ea..0000000 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/refund/PayRefundMapper.java +++ /dev/null @@ -1,78 +0,0 @@ -package cn.iocoder.yudao.module.pay.dal.mysql.refund; - -import cn.iocoder.yudao.framework.common.pojo.PageResult; -import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; -import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; -import cn.iocoder.yudao.module.pay.controller.admin.refund.vo.PayRefundExportReqVO; -import cn.iocoder.yudao.module.pay.controller.admin.refund.vo.PayRefundPageReqVO; -import cn.iocoder.yudao.module.pay.dal.dataobject.refund.PayRefundDO; -import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; -import org.apache.ibatis.annotations.Mapper; - -import java.util.List; - -@Mapper -public interface PayRefundMapper extends BaseMapperX { - - default Long selectCountByAppId(Long appId) { - return selectCount(PayRefundDO::getAppId, appId); - } - - default PayRefundDO selectByAppIdAndMerchantRefundId(Long appId, String merchantRefundId) { - return selectOne(new LambdaQueryWrapperX() - .eq(PayRefundDO::getAppId, appId) - .eq(PayRefundDO::getMerchantRefundId, merchantRefundId)); - } - - default Long selectCountByAppIdAndOrderId(Long appId, Long orderId, Integer status) { - return selectCount(new LambdaQueryWrapperX() - .eq(PayRefundDO::getAppId, appId) - .eq(PayRefundDO::getOrderId, orderId) - .eq(PayRefundDO::getStatus, status)); - } - - default PayRefundDO selectByAppIdAndNo(Long appId, String no) { - return selectOne(new LambdaQueryWrapperX() - .eq(PayRefundDO::getAppId, appId) - .eq(PayRefundDO::getNo, no)); - } - - default PayRefundDO selectByNo(String no) { - return selectOne(PayRefundDO::getNo, no); - } - - default int updateByIdAndStatus(Long id, Integer status, PayRefundDO update) { - return update(update, new LambdaQueryWrapper() - .eq(PayRefundDO::getId, id).eq(PayRefundDO::getStatus, status)); - } - - default PageResult selectPage(PayRefundPageReqVO reqVO) { - return selectPage(reqVO, new LambdaQueryWrapperX() - .eqIfPresent(PayRefundDO::getAppId, reqVO.getAppId()) - .eqIfPresent(PayRefundDO::getChannelCode, reqVO.getChannelCode()) - .likeIfPresent(PayRefundDO::getMerchantOrderId, reqVO.getMerchantOrderId()) - .likeIfPresent(PayRefundDO::getMerchantRefundId, reqVO.getMerchantRefundId()) - .likeIfPresent(PayRefundDO::getChannelOrderNo, reqVO.getChannelOrderNo()) - .likeIfPresent(PayRefundDO::getChannelRefundNo, reqVO.getChannelRefundNo()) - .eqIfPresent(PayRefundDO::getStatus, reqVO.getStatus()) - .betweenIfPresent(PayRefundDO::getCreateTime, reqVO.getCreateTime()) - .orderByDesc(PayRefundDO::getId)); - } - - default List selectList(PayRefundExportReqVO reqVO) { - return selectList(new LambdaQueryWrapperX() - .eqIfPresent(PayRefundDO::getAppId, reqVO.getAppId()) - .eqIfPresent(PayRefundDO::getChannelCode, reqVO.getChannelCode()) - .likeIfPresent(PayRefundDO::getMerchantOrderId, reqVO.getMerchantOrderId()) - .likeIfPresent(PayRefundDO::getMerchantRefundId, reqVO.getMerchantRefundId()) - .likeIfPresent(PayRefundDO::getChannelOrderNo, reqVO.getChannelOrderNo()) - .likeIfPresent(PayRefundDO::getChannelRefundNo, reqVO.getChannelRefundNo()) - .eqIfPresent(PayRefundDO::getStatus, reqVO.getStatus()) - .betweenIfPresent(PayRefundDO::getCreateTime, reqVO.getCreateTime()) - .orderByDesc(PayRefundDO::getId)); - } - - default List selectListByStatus(Integer status) { - return selectList(PayRefundDO::getStatus, status); - } -} diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/transfer/PayTransferMapper.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/transfer/PayTransferMapper.java deleted file mode 100644 index 59e6b3e..0000000 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/transfer/PayTransferMapper.java +++ /dev/null @@ -1,65 +0,0 @@ -package cn.iocoder.yudao.module.pay.dal.mysql.transfer; - -import cn.iocoder.yudao.framework.common.pojo.PageResult; -import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; -import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; -import cn.iocoder.yudao.module.pay.controller.admin.transfer.vo.PayTransferPageReqVO; -import cn.iocoder.yudao.module.pay.dal.dataobject.transfer.PayTransferDO; -import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; -import org.apache.ibatis.annotations.Mapper; - -import java.util.Collection; -import java.util.List; - -@Mapper -public interface PayTransferMapper extends BaseMapperX { - - default int updateByIdAndStatus(Long id, List whereStatuses, PayTransferDO updateObj) { - return update(updateObj, new LambdaQueryWrapper() - .eq(PayTransferDO::getId, id) - .in(PayTransferDO::getStatus, whereStatuses)); - } - - default int updateByIdAndStatus(Long id, Integer whereStatus, PayTransferDO updateObj) { - return update(updateObj, new LambdaQueryWrapper() - .eq(PayTransferDO::getId, id) - .eq(PayTransferDO::getStatus, whereStatus)); - } - - default PayTransferDO selectByAppIdAndMerchantOrderId(Long appId, String merchantOrderId) { - return selectOne(PayTransferDO::getAppId, appId, - PayTransferDO::getMerchantTransferId, merchantOrderId); - } - - default PageResult selectPage(PayTransferPageReqVO reqVO) { - return selectPage(reqVO, new LambdaQueryWrapperX() - .eqIfPresent(PayTransferDO::getNo, reqVO.getNo()) - .eqIfPresent(PayTransferDO::getAppId, reqVO.getAppId()) - .eqIfPresent(PayTransferDO::getChannelCode, reqVO.getChannelCode()) - .eqIfPresent(PayTransferDO::getMerchantTransferId, reqVO.getMerchantOrderId()) - .eqIfPresent(PayTransferDO::getStatus, reqVO.getStatus()) - .likeIfPresent(PayTransferDO::getUserName, reqVO.getUserName()) - .likeIfPresent(PayTransferDO::getUserAccount, reqVO.getUserAccount()) - .eqIfPresent(PayTransferDO::getChannelTransferNo, reqVO.getChannelTransferNo()) - .betweenIfPresent(PayTransferDO::getCreateTime, reqVO.getCreateTime()) - .orderByDesc(PayTransferDO::getId)); - } - - default List selectListByStatus(Collection statuses) { - return selectList(PayTransferDO::getStatus, statuses); - } - - default PayTransferDO selectByAppIdAndNo(Long appId, String no) { - return selectOne(PayTransferDO::getAppId, appId, - PayTransferDO::getNo, no); - } - - default PayTransferDO selectByNo(String no) { - return selectOne(PayTransferDO::getNo, no); - } - -} - - - - diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/wallet/PayWalletMapper.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/wallet/PayWalletMapper.java deleted file mode 100644 index 19a76cd..0000000 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/wallet/PayWalletMapper.java +++ /dev/null @@ -1,134 +0,0 @@ -package cn.iocoder.yudao.module.pay.dal.mysql.wallet; - - -import cn.iocoder.yudao.framework.common.pojo.PageResult; -import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; -import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; -import cn.iocoder.yudao.module.pay.controller.admin.wallet.vo.wallet.PayWalletPageReqVO; -import cn.iocoder.yudao.module.pay.dal.dataobject.wallet.PayWalletDO; -import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; -import org.apache.ibatis.annotations.Mapper; - -@Mapper -public interface PayWalletMapper extends BaseMapperX { - - default PayWalletDO selectByUserIdAndType(Long userId, Integer userType) { - return selectOne(PayWalletDO::getUserId, userId, - PayWalletDO::getUserType, userType); - } - - default PageResult selectPage(PayWalletPageReqVO reqVO) { - return selectPage(reqVO, new LambdaQueryWrapperX() - .eqIfPresent(PayWalletDO::getUserId, reqVO.getUserId()) - .eqIfPresent(PayWalletDO::getUserType, reqVO.getUserType()) - .betweenIfPresent(PayWalletDO::getCreateTime, reqVO.getCreateTime()) - .orderByDesc(PayWalletDO::getId)); - } - - /** - * 当消费退款时候, 更新钱包 - * - * @param id 钱包 id - * @param price 消费金额 - */ - default int updateWhenConsumptionRefund(Long id, Integer price) { - LambdaUpdateWrapper lambdaUpdateWrapper = new LambdaUpdateWrapper() - .setSql(" balance = balance + " + price - + ", total_expense = total_expense - " + price) - .eq(PayWalletDO::getId, id); - return update(null, lambdaUpdateWrapper); - } - - /** - * 当消费时候, 更新钱包 - * - * @param price 消费金额 - * @param id 钱包 id - */ - default int updateWhenConsumption(Long id, Integer price){ - LambdaUpdateWrapper lambdaUpdateWrapper = new LambdaUpdateWrapper() - .setSql(" balance = balance - " + price - + ", total_expense = total_expense + " + price) - .eq(PayWalletDO::getId, id) - .ge(PayWalletDO::getBalance, price); // cas 逻辑 - return update(null, lambdaUpdateWrapper); - } - - /** - * 当充值的时候,更新钱包 - * - * @param id 钱包 id - * @param price 钱包金额 - */ - default int updateWhenRecharge(Long id, Integer price){ - LambdaUpdateWrapper lambdaUpdateWrapper = new LambdaUpdateWrapper() - .setSql(" balance = balance + " + price - + ", total_recharge = total_recharge + " + price) - .eq(PayWalletDO::getId, id); - return update(null, lambdaUpdateWrapper); - } - - /** - * 增加余额的时候,更新钱包 - * - * @param id 钱包 id - * @param price 钱包金额 - */ - default void updateWhenAdd(Long id, Integer price) { - LambdaUpdateWrapper lambdaUpdateWrapper = new LambdaUpdateWrapper() - .setSql(" balance = balance + " + price) - .eq(PayWalletDO::getId, id); - update(null, lambdaUpdateWrapper); - } - - /** - * 冻结钱包部分余额 - * - * @param id 钱包 id - * @param price 冻结金额 - */ - default int freezePrice(Long id, Integer price){ - LambdaUpdateWrapper lambdaUpdateWrapper = new LambdaUpdateWrapper() - .setSql(" balance = balance - " + price - + ", freeze_price = freeze_price + " + price) - .eq(PayWalletDO::getId, id) - .ge(PayWalletDO::getBalance, price); // cas 逻辑 - return update(null, lambdaUpdateWrapper); - } - - /** - * 解冻钱包余额 - * - * @param id 钱包 id - * @param price 解冻金额 - */ - default int unFreezePrice(Long id, Integer price){ - LambdaUpdateWrapper lambdaUpdateWrapper = new LambdaUpdateWrapper() - .setSql(" balance = balance + " + price - + ", freeze_price = freeze_price - " + price) - .eq(PayWalletDO::getId, id) - .ge(PayWalletDO::getFreezePrice, price); // cas 逻辑 - return update(null, lambdaUpdateWrapper); - } - - /** - * 当充值退款时, 更新钱包 - * - * @param id 钱包 id - * @param price 退款金额 - */ - default int updateWhenRechargeRefund(Long id, Integer price){ - LambdaUpdateWrapper lambdaUpdateWrapper = new LambdaUpdateWrapper() - .setSql(" freeze_price = freeze_price - " + price - + ", total_recharge = total_recharge - " + price) - .eq(PayWalletDO::getId, id) - .ge(PayWalletDO::getFreezePrice, price) - .ge(PayWalletDO::getTotalRecharge, price);// cas 逻辑 - return update(null, lambdaUpdateWrapper); - } - -} - - - - diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/wallet/PayWalletRechargeMapper.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/wallet/PayWalletRechargeMapper.java deleted file mode 100644 index f82b885..0000000 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/wallet/PayWalletRechargeMapper.java +++ /dev/null @@ -1,31 +0,0 @@ -package cn.iocoder.yudao.module.pay.dal.mysql.wallet; - -import cn.iocoder.yudao.framework.common.pojo.PageParam; -import cn.iocoder.yudao.framework.common.pojo.PageResult; -import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; -import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; -import cn.iocoder.yudao.module.pay.dal.dataobject.wallet.PayWalletRechargeDO; -import cn.iocoder.yudao.module.pay.dal.dataobject.wallet.PayWalletRechargePackageDO; -import org.apache.ibatis.annotations.Mapper; - -@Mapper -public interface PayWalletRechargeMapper extends BaseMapperX { - - default int updateByIdAndPaid(Long id, boolean wherePayStatus, PayWalletRechargeDO updateObj) { - return update(updateObj, new LambdaQueryWrapperX() - .eq(PayWalletRechargeDO::getId, id).eq(PayWalletRechargeDO::getPayStatus, wherePayStatus)); - } - - default int updateByIdAndRefunded(Long id, Integer whereRefundStatus, PayWalletRechargeDO updateObj) { - return update(updateObj, new LambdaQueryWrapperX() - .eq(PayWalletRechargeDO::getId, id).eq(PayWalletRechargeDO::getRefundStatus, whereRefundStatus)); - } - - default PageResult selectPage(PageParam pageReqVO, Long walletId, Boolean payStatus) { - return selectPage(pageReqVO, new LambdaQueryWrapperX() - .eq(PayWalletRechargeDO::getWalletId, walletId) - .eq(PayWalletRechargeDO::getPayStatus, payStatus) - .orderByDesc(PayWalletRechargeDO::getId)); - } - -} \ No newline at end of file diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/wallet/PayWalletRechargePackageMapper.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/wallet/PayWalletRechargePackageMapper.java deleted file mode 100644 index 821908c..0000000 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/wallet/PayWalletRechargePackageMapper.java +++ /dev/null @@ -1,32 +0,0 @@ -package cn.iocoder.yudao.module.pay.dal.mysql.wallet; - - -import cn.iocoder.yudao.framework.common.pojo.PageResult; -import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; -import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; -import cn.iocoder.yudao.module.pay.controller.admin.wallet.vo.rechargepackage.WalletRechargePackagePageReqVO; -import cn.iocoder.yudao.module.pay.dal.dataobject.wallet.PayWalletRechargePackageDO; -import org.apache.ibatis.annotations.Mapper; - -import java.util.List; - -@Mapper -public interface PayWalletRechargePackageMapper extends BaseMapperX { - - default PageResult selectPage(WalletRechargePackagePageReqVO reqVO) { - return selectPage(reqVO, new LambdaQueryWrapperX() - .likeIfPresent(PayWalletRechargePackageDO::getName, reqVO.getName()) - .eqIfPresent(PayWalletRechargePackageDO::getStatus, reqVO.getStatus()) - .betweenIfPresent(PayWalletRechargePackageDO::getCreateTime, reqVO.getCreateTime()) - .orderByDesc(PayWalletRechargePackageDO::getPayPrice)); - } - - default PayWalletRechargePackageDO selectByName(String name) { - return selectOne(PayWalletRechargePackageDO::getName, name); - } - - default List selectListByStatus(Integer status) { - return selectList(PayWalletRechargePackageDO::getStatus, status); - } - -} diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/wallet/PayWalletTransactionMapper.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/wallet/PayWalletTransactionMapper.java deleted file mode 100644 index 212a90c..0000000 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/wallet/PayWalletTransactionMapper.java +++ /dev/null @@ -1,67 +0,0 @@ -package cn.iocoder.yudao.module.pay.dal.mysql.wallet; - - -import cn.hutool.core.collection.CollUtil; -import cn.hutool.core.map.MapUtil; -import cn.iocoder.yudao.framework.common.pojo.PageParam; -import cn.iocoder.yudao.framework.common.pojo.PageResult; -import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; -import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; -import cn.iocoder.yudao.framework.mybatis.core.query.QueryWrapperX; -import cn.iocoder.yudao.module.pay.controller.app.wallet.vo.transaction.AppPayWalletTransactionPageReqVO; -import cn.iocoder.yudao.module.pay.dal.dataobject.wallet.PayWalletTransactionDO; -import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; -import org.apache.ibatis.annotations.Mapper; - -import java.time.LocalDateTime; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.function.Consumer; - -import static cn.iocoder.yudao.module.pay.controller.app.wallet.vo.transaction.AppPayWalletTransactionPageReqVO.*; - -@Mapper -public interface PayWalletTransactionMapper extends BaseMapperX { - - default PageResult selectPage(Long walletId, Integer type, - PageParam pageParam, LocalDateTime[] createTime) { - LambdaQueryWrapperX query = new LambdaQueryWrapperX() - .eqIfPresent(PayWalletTransactionDO::getWalletId, walletId); - if (Objects.equals(type, TYPE_INCOME)) { - query.gt(PayWalletTransactionDO::getPrice, 0); - } else if (Objects.equals(type, TYPE_EXPENSE)) { - query.lt(PayWalletTransactionDO::getPrice, 0); - } - query.betweenIfPresent(PayWalletTransactionDO::getCreateTime, createTime); - query.orderByDesc(PayWalletTransactionDO::getId); - return selectPage(pageParam, query); - } - - default Integer selectPriceSum(Long walletId, Integer type, LocalDateTime[] createTime) { - // SQL sum 查询 - List> result = selectMaps(new QueryWrapperX() - .select("SUM(price) AS priceSum") - .gt(Objects.equals(type, TYPE_INCOME), "price", 0) // 收入 - .lt(Objects.equals(type, TYPE_EXPENSE), "price", 0) // 支出 - .eq("wallet_id", walletId) - .between("create_time", createTime[0], createTime[1])); - // 获得 sum 结果 - Map first = CollUtil.getFirst(result); - return MapUtil.getInt(first, "priceSum", 0); - } - - default PayWalletTransactionDO selectByNo(String no) { - return selectOne(PayWalletTransactionDO::getNo, no); - } - - default PayWalletTransactionDO selectByBiz(String bizId, Integer bizType) { - return selectOne(PayWalletTransactionDO::getBizId, bizId, - PayWalletTransactionDO::getBizType, bizType); - } - -} - - - - diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/redis/RedisKeyConstants.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/redis/RedisKeyConstants.java deleted file mode 100644 index 6de0e21..0000000 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/redis/RedisKeyConstants.java +++ /dev/null @@ -1,36 +0,0 @@ -package cn.iocoder.yudao.module.pay.dal.redis; - -/** - * 支付 Redis Key 枚举类 - * - * @author 芋道源码 - */ -public interface RedisKeyConstants { - - /** - * 通知任务的分布式锁 - * - * KEY 格式:pay_notify:lock:%d // 参数来自 DefaultLockKeyBuilder 类 - * VALUE 数据格式:HASH // RLock.class:Redisson 的 Lock 锁,使用 Hash 数据结构 - * 过期时间:不固定 - */ - String PAY_NOTIFY_LOCK = "pay_notify:lock:%d"; - - /** - * 支付钱包的分布式锁 - * - * KEY 格式:pay_wallet:lock:%d - * VALUE 数据格式:HASH // RLock.class:Redisson 的 Lock 锁,使用 Hash 数据结构 - * 过期时间:不固定 - */ - String PAY_WALLET_LOCK = "pay_wallet:lock:%d"; - - /** - * 支付序号的缓存 - * - * KEY 格式:pay_no:{prefix} - * VALUE 数据格式:编号自增 - */ - String PAY_NO = "pay_no:"; - -} diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/redis/no/PayNoRedisDAO.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/redis/no/PayNoRedisDAO.java deleted file mode 100644 index 1482ddf..0000000 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/redis/no/PayNoRedisDAO.java +++ /dev/null @@ -1,39 +0,0 @@ -package cn.iocoder.yudao.module.pay.dal.redis.no; - -import cn.hutool.core.date.DatePattern;import cn.hutool.core.date.DateUtil; -import cn.iocoder.yudao.module.pay.dal.redis.RedisKeyConstants; -import org.springframework.data.redis.core.StringRedisTemplate; -import org.springframework.stereotype.Repository; - -import jakarta.annotation.Resource; -import java.time.Duration; -import java.time.LocalDateTime; - -/** - * 支付序号的 Redis DAO - * - * @author 芋道源码 - */ -@Repository -public class PayNoRedisDAO { - - @Resource - private StringRedisTemplate stringRedisTemplate; - - /** - * 生成序号 - * - * @param prefix 前缀 - * @return 序号 - */ - public String generate(String prefix) { - // 递增序号 - String noPrefix = prefix + DateUtil.format(LocalDateTime.now(), DatePattern.PURE_DATETIME_PATTERN); - String key = RedisKeyConstants.PAY_NO + noPrefix; - Long no = stringRedisTemplate.opsForValue().increment(key); - // 设置过期时间 - stringRedisTemplate.expire(key, Duration.ofMinutes(1L)); - return noPrefix + no; - } - -} diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/redis/notify/PayNotifyLockRedisDAO.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/redis/notify/PayNotifyLockRedisDAO.java deleted file mode 100644 index b5916f6..0000000 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/redis/notify/PayNotifyLockRedisDAO.java +++ /dev/null @@ -1,39 +0,0 @@ -package cn.iocoder.yudao.module.pay.dal.redis.notify; - -import org.redisson.api.RLock; -import org.redisson.api.RedissonClient; -import org.springframework.stereotype.Repository; - -import jakarta.annotation.Resource; -import java.util.concurrent.TimeUnit; - -import static cn.iocoder.yudao.module.pay.dal.redis.RedisKeyConstants.PAY_NOTIFY_LOCK; - -/** - * 支付通知的锁 Redis DAO - * - * @author 芋道源码 - */ -@Repository -public class PayNotifyLockRedisDAO { - - @Resource - private RedissonClient redissonClient; - - public void lock(Long id, Long timeoutMillis, Runnable runnable) { - String lockKey = formatKey(id); - RLock lock = redissonClient.getLock(lockKey); - try { - lock.lock(timeoutMillis, TimeUnit.MILLISECONDS); - // 执行逻辑 - runnable.run(); - } finally { - lock.unlock(); - } - } - - private static String formatKey(Long id) { - return String.format(PAY_NOTIFY_LOCK, id); - } - -} diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/redis/wallet/PayWalletLockRedisDAO.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/redis/wallet/PayWalletLockRedisDAO.java deleted file mode 100644 index 1c42727..0000000 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/redis/wallet/PayWalletLockRedisDAO.java +++ /dev/null @@ -1,42 +0,0 @@ -package cn.iocoder.yudao.module.pay.dal.redis.wallet; - -import jakarta.annotation.Resource; -import org.redisson.api.RLock; -import org.redisson.api.RedissonClient; -import org.springframework.stereotype.Repository; - -import java.util.concurrent.Callable; -import java.util.concurrent.TimeUnit; - -import static cn.iocoder.yudao.module.pay.dal.redis.RedisKeyConstants.PAY_WALLET_LOCK; - -/** - * 支付钱包的锁 Redis DAO - * - * @author 芋道源码 - */ -@Repository -public class PayWalletLockRedisDAO { - - @Resource - private RedissonClient redissonClient; - - public V lock(Long id, Long timeoutMillis, Callable callable) throws Exception { - String lockKey = formatKey(id); - RLock lock = redissonClient.getLock(lockKey); - try { - lock.lock(timeoutMillis, TimeUnit.MILLISECONDS); - // 执行逻辑 - return callable.call(); - } catch (Exception e) { - throw e; - } finally { - lock.unlock(); - } - } - - private static String formatKey(Long id) { - return String.format(PAY_WALLET_LOCK, id); - } - -} diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/job/config/PayJobConfiguration.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/job/config/PayJobConfiguration.java deleted file mode 100644 index cd6bd0b..0000000 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/job/config/PayJobConfiguration.java +++ /dev/null @@ -1,28 +0,0 @@ -package cn.iocoder.yudao.module.pay.framework.job.config; - -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; - -import java.util.concurrent.ThreadPoolExecutor; - -@Configuration(proxyBeanMethods = false) -public class PayJobConfiguration { - - public static final String NOTIFY_THREAD_POOL_TASK_EXECUTOR = "NOTIFY_THREAD_POOL_TASK_EXECUTOR"; - - @Bean(NOTIFY_THREAD_POOL_TASK_EXECUTOR) - public ThreadPoolTaskExecutor notifyThreadPoolTaskExecutor() { - ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); - executor.setCorePoolSize(8); // 设置核心线程数 - executor.setMaxPoolSize(16); // 设置最大线程数 - executor.setKeepAliveSeconds(60); // 设置空闲时间 - executor.setQueueCapacity(100); // 设置队列大小 - executor.setThreadNamePrefix("notify-task-"); // 配置线程池的前缀 - executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy()); - // 进行加载 - executor.initialize(); - return executor; - } - -} diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/job/core/package-info.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/job/core/package-info.java deleted file mode 100644 index 5a5a689..0000000 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/job/core/package-info.java +++ /dev/null @@ -1,4 +0,0 @@ -/** - * 占位 - */ -package cn.iocoder.yudao.module.pay.framework.job.core; diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/package-info.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/package-info.java deleted file mode 100644 index a128682..0000000 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/package-info.java +++ /dev/null @@ -1,6 +0,0 @@ -/** - * 属于 pay 模块的 framework 封装 - * - * @author 芋道源码 - */ -package cn.iocoder.yudao.module.pay.framework; 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 deleted file mode 100644 index 47c9b9f..0000000 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/config/PayConfiguration.java +++ /dev/null @@ -1,18 +0,0 @@ -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/config/PayProperties.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/config/PayProperties.java deleted file mode 100644 index 84905a5..0000000 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/config/PayProperties.java +++ /dev/null @@ -1,69 +0,0 @@ -package cn.iocoder.yudao.module.pay.framework.pay.config; - -import lombok.Data; -import org.hibernate.validator.constraints.URL; -import org.springframework.boot.context.properties.ConfigurationProperties; -import org.springframework.validation.annotation.Validated; - -import jakarta.validation.constraints.NotEmpty; - -@ConfigurationProperties(prefix = "yudao.pay") -@Validated -@Data -public class PayProperties { - - private static final String ORDER_NO_PREFIX = "P"; - private static final String REFUND_NO_PREFIX = "R"; - - private static final String WALLET_PAY_APP_KEY_DEFAULT = "wallet"; - - /** - * 支付回调地址 - * - * 实际上,对应的 PayNotifyController 的 notifyOrder 方法的 URL - * - * 回调顺序:支付渠道(支付宝支付、微信支付) => yudao-module-pay 的 orderNotifyUrl 地址 => 业务的 PayAppDO.orderNotifyUrl 地址 - */ - @NotEmpty(message = "支付回调地址不能为空") - @URL(message = "支付回调地址的格式必须是 URL") - private String orderNotifyUrl; - - /** - * 退款回调地址 - * - * 实际上,对应的 PayNotifyController 的 notifyRefund 方法的 URL - * - * 回调顺序:支付渠道(支付宝支付、微信支付) => yudao-module-pay 的 refundNotifyUrl 地址 => 业务的 PayAppDO.notifyRefundUrl 地址 - */ - @NotEmpty(message = "支付回调地址不能为空") - @URL(message = "支付回调地址的格式必须是 URL") - private String refundNotifyUrl; - - /** - * 转账回调地址 - * - * 实际上,对应的 PayNotifyController 的 notifyTransfer 方法的 URL - * - * 回调顺序:支付渠道(支付宝支付、微信支付) => yudao-module-pay 的 transferNotifyUrl 地址 => 业务的 PayAppDO.transferNotifyUrl 地址 - */ - private String transferNotifyUrl; - - /** - * 支付订单 no 的前缀 - */ - @NotEmpty(message = "支付订单 no 的前缀不能为空") - private String orderNoPrefix = ORDER_NO_PREFIX; - - /** - * 退款订单 no 的前缀 - */ - @NotEmpty(message = "退款订单 no 的前缀不能为空") - private String refundNoPrefix = REFUND_NO_PREFIX; - - /** - * 钱包支付应用 AppKey - */ - @NotEmpty(message = "钱包支付应用 AppKey 不能为空") - private String walletPayAppKey = WALLET_PAY_APP_KEY_DEFAULT; - -} 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 deleted file mode 100644 index 4748c40..0000000 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/PayClient.java +++ /dev/null @@ -1,118 +0,0 @@ -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 deleted file mode 100644 index d0c307d..0000000 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/PayClientConfig.java +++ /dev/null @@ -1,27 +0,0 @@ -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 deleted file mode 100644 index 35b7b95..0000000 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/PayClientFactory.java +++ /dev/null @@ -1,28 +0,0 @@ -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 deleted file mode 100644 index 3d6ce86..0000000 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/dto/order/PayOrderRespDTO.java +++ /dev/null @@ -1,141 +0,0 @@ -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 deleted file mode 100644 index ae9fc24..0000000 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/dto/order/PayOrderUnifiedReqDTO.java +++ /dev/null @@ -1,92 +0,0 @@ -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 deleted file mode 100644 index fbe945b..0000000 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/dto/refund/PayRefundRespDTO.java +++ /dev/null @@ -1,115 +0,0 @@ -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 deleted file mode 100644 index 08d826c..0000000 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/dto/refund/PayRefundUnifiedReqDTO.java +++ /dev/null @@ -1,70 +0,0 @@ -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 deleted file mode 100644 index 81dbc9f..0000000 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/dto/transfer/PayTransferRespDTO.java +++ /dev/null @@ -1,116 +0,0 @@ -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 deleted file mode 100644 index d7ea772..0000000 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/dto/transfer/PayTransferUnifiedReqDTO.java +++ /dev/null @@ -1,73 +0,0 @@ -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 deleted file mode 100644 index 8cedc4e..0000000 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/exception/PayClientException.java +++ /dev/null @@ -1,17 +0,0 @@ -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 deleted file mode 100644 index fce355e..0000000 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/impl/AbstractPayClient.java +++ /dev/null @@ -1,251 +0,0 @@ -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 deleted file mode 100644 index d4b352f..0000000 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/impl/NonePayClientConfig.java +++ /dev/null @@ -1,31 +0,0 @@ -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 deleted file mode 100644 index 6b907cd..0000000 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/impl/PayClientFactoryImpl.java +++ /dev/null @@ -1,97 +0,0 @@ -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 deleted file mode 100644 index fee7cdd..0000000 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/impl/alipay/AbstractAlipayPayClient.java +++ /dev/null @@ -1,379 +0,0 @@ -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 deleted file mode 100644 index ff878b1..0000000 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/impl/alipay/AlipayAppPayClient.java +++ /dev/null @@ -1,60 +0,0 @@ -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 deleted file mode 100644 index 2f5245f..0000000 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/impl/alipay/AlipayBarPayClient.java +++ /dev/null @@ -1,86 +0,0 @@ -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 deleted file mode 100644 index 431cf17..0000000 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/impl/alipay/AlipayPayClientConfig.java +++ /dev/null @@ -1,128 +0,0 @@ -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 deleted file mode 100644 index 9d90879..0000000 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/impl/alipay/AlipayPcPayClient.java +++ /dev/null @@ -1,70 +0,0 @@ -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 deleted file mode 100644 index 549e7d0..0000000 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/impl/alipay/AlipayQrPayClient.java +++ /dev/null @@ -1,66 +0,0 @@ -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 deleted file mode 100644 index 5798d98..0000000 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/impl/alipay/AlipayWapPayClient.java +++ /dev/null @@ -1,59 +0,0 @@ -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 deleted file mode 100644 index 01ba8c5..0000000 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/impl/mock/MockPayClient.java +++ /dev/null @@ -1,84 +0,0 @@ -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 deleted file mode 100644 index 8021dc7..0000000 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/impl/wallet/WalletPayClient.java +++ /dev/null @@ -1,251 +0,0 @@ -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 deleted file mode 100644 index 5f38b1a..0000000 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/impl/weixin/AbstractWxPayClient.java +++ /dev/null @@ -1,597 +0,0 @@ -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 deleted file mode 100644 index ae60f29..0000000 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/impl/weixin/WxAppPayClient.java +++ /dev/null @@ -1,63 +0,0 @@ -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 deleted file mode 100644 index 813342c..0000000 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/impl/weixin/WxBarPayClient.java +++ /dev/null @@ -1,107 +0,0 @@ -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 deleted file mode 100644 index 2f2560b..0000000 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/impl/weixin/WxLitePayClient.java +++ /dev/null @@ -1,22 +0,0 @@ -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 deleted file mode 100644 index bc7e4fe..0000000 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/impl/weixin/WxNativePayClient.java +++ /dev/null @@ -1,59 +0,0 @@ -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 deleted file mode 100644 index 5aa812b..0000000 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/impl/weixin/WxPayClientConfig.java +++ /dev/null @@ -1,107 +0,0 @@ -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 deleted file mode 100644 index 322e9b1..0000000 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/impl/weixin/WxPubPayClient.java +++ /dev/null @@ -1,81 +0,0 @@ -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 deleted file mode 100644 index 7cfbca4..0000000 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/impl/weixin/WxWapPayClient.java +++ /dev/null @@ -1,62 +0,0 @@ -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 deleted file mode 100644 index 182defb..0000000 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/enums/PayOrderDisplayModeEnum.java +++ /dev/null @@ -1,29 +0,0 @@ -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 deleted file mode 100644 index 90f4577..0000000 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/package-info.java +++ /dev/null @@ -1,6 +0,0 @@ -/** - * 支付客户端,接入国内多个支付渠道: - * 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/framework/web/config/PayWebConfiguration.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/web/config/PayWebConfiguration.java deleted file mode 100644 index 4a9dbb4..0000000 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/web/config/PayWebConfiguration.java +++ /dev/null @@ -1,24 +0,0 @@ -package cn.iocoder.yudao.module.pay.framework.web.config; - -import cn.iocoder.yudao.framework.swagger.config.YudaoSwaggerAutoConfiguration; -import org.springdoc.core.models.GroupedOpenApi; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; - -/** - * pay 模块的 web 组件的 Configuration - * - * @author 芋道源码 - */ -@Configuration(proxyBeanMethods = false) -public class PayWebConfiguration { - - /** - * pay 模块的 API 分组 - */ - @Bean - public GroupedOpenApi payGroupedOpenApi() { - return YudaoSwaggerAutoConfiguration.buildGroupedOpenApi("pay"); - } - -} diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/web/package-info.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/web/package-info.java deleted file mode 100644 index 03bfad9..0000000 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/web/package-info.java +++ /dev/null @@ -1,4 +0,0 @@ -/** - * pay 模块的 web 配置 - */ -package cn.iocoder.yudao.module.pay.framework.web; diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/job/notify/PayNotifyJob.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/job/notify/PayNotifyJob.java deleted file mode 100644 index e95bb0a..0000000 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/job/notify/PayNotifyJob.java +++ /dev/null @@ -1,31 +0,0 @@ -package cn.iocoder.yudao.module.pay.job.notify; - -import cn.iocoder.yudao.framework.quartz.core.handler.JobHandler; -import cn.iocoder.yudao.framework.tenant.core.job.TenantJob; -import cn.iocoder.yudao.module.pay.service.notify.PayNotifyService; -import lombok.extern.slf4j.Slf4j; -import org.springframework.stereotype.Component; - -import jakarta.annotation.Resource; - -/** - * 支付通知 Job - * 通过不断扫描待通知的 PayNotifyTaskDO 记录,回调业务线的回调接口 - * - * @author 芋道源码 - */ -@Component -@Slf4j -public class PayNotifyJob implements JobHandler { - - @Resource - private PayNotifyService payNotifyService; - - @Override - @TenantJob - public String execute(String param) throws Exception { - int notifyCount = payNotifyService.executeNotify(); - return String.format("执行支付通知 %s 个", notifyCount); - } - -} diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/job/order/PayOrderExpireJob.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/job/order/PayOrderExpireJob.java deleted file mode 100644 index 8f95737..0000000 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/job/order/PayOrderExpireJob.java +++ /dev/null @@ -1,31 +0,0 @@ -package cn.iocoder.yudao.module.pay.job.order; - -import cn.hutool.core.util.StrUtil; -import cn.iocoder.yudao.framework.quartz.core.handler.JobHandler; -import cn.iocoder.yudao.framework.tenant.core.job.TenantJob; -import cn.iocoder.yudao.module.pay.service.order.PayOrderService; -import org.springframework.stereotype.Component; - -import jakarta.annotation.Resource; - -/** - * 支付订单的过期 Job - * - * 支付超过过期时间时,支付渠道是不会通知进行过期,所以需要定时进行过期关闭。 - * - * @author 芋道源码 - */ -@Component -public class PayOrderExpireJob implements JobHandler { - - @Resource - private PayOrderService orderService; - - @Override - @TenantJob - public String execute(String param) { - int count = orderService.expireOrder(); - return StrUtil.format("支付过期 {} 个", count); - } - -} diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/job/order/PayOrderSyncJob.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/job/order/PayOrderSyncJob.java deleted file mode 100644 index 40fcfe9..0000000 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/job/order/PayOrderSyncJob.java +++ /dev/null @@ -1,43 +0,0 @@ -package cn.iocoder.yudao.module.pay.job.order; - -import cn.hutool.core.util.StrUtil; -import cn.iocoder.yudao.framework.quartz.core.handler.JobHandler; -import cn.iocoder.yudao.framework.tenant.core.job.TenantJob; -import cn.iocoder.yudao.module.pay.service.order.PayOrderService; -import org.springframework.stereotype.Component; - -import jakarta.annotation.Resource; -import java.time.Duration; -import java.time.LocalDateTime; - -/** - * 支付订单的同步 Job - * - * 由于支付订单的状态,是由支付渠道异步通知进行同步,考虑到异步通知可能会失败(小概率),所以需要定时进行同步。 - * - * @author 芋道源码 - */ -@Component -public class PayOrderSyncJob implements JobHandler { - - /** - * 同步创建时间在 N 分钟之内的订单 - * - * 为什么同步 10 分钟之内的订单? - * 因为一个订单发起支付,到支付成功,大多数在 10 分钟内,需要保证轮询到。 - * 如果设置为 30、60 或者更大时间范围,会导致轮询的订单太多,影响性能。当然,你也可以根据自己的业务情况来处理。 - */ - private static final Duration CREATE_TIME_DURATION_BEFORE = Duration.ofMinutes(10); - - @Resource - private PayOrderService orderService; - - @Override - @TenantJob - public String execute(String param) { - LocalDateTime minCreateTime = LocalDateTime.now().minus(CREATE_TIME_DURATION_BEFORE); - int count = orderService.syncOrder(minCreateTime); - return StrUtil.format("同步支付订单 {} 个", count); - } - -} diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/job/refund/PayRefundSyncJob.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/job/refund/PayRefundSyncJob.java deleted file mode 100644 index 0d5c850..0000000 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/job/refund/PayRefundSyncJob.java +++ /dev/null @@ -1,31 +0,0 @@ -package cn.iocoder.yudao.module.pay.job.refund; - -import cn.hutool.core.util.StrUtil; -import cn.iocoder.yudao.framework.quartz.core.handler.JobHandler; -import cn.iocoder.yudao.framework.tenant.core.job.TenantJob; -import cn.iocoder.yudao.module.pay.service.refund.PayRefundService; -import org.springframework.stereotype.Component; - -import jakarta.annotation.Resource; - -/** - * 退款订单的同步 Job - * - * 由于退款订单的状态,是由支付渠道异步通知进行同步,考虑到异步通知可能会失败(小概率),所以需要定时进行同步。 - * - * @author 芋道源码 - */ -@Component -public class PayRefundSyncJob implements JobHandler { - - @Resource - private PayRefundService refundService; - - @Override - @TenantJob - public String execute(String param) { - int count = refundService.syncRefund(); - return StrUtil.format("同步退款订单 {} 个", count); - } - -} diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/job/transfer/PayTransferSyncJob.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/job/transfer/PayTransferSyncJob.java deleted file mode 100644 index d8f1321..0000000 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/job/transfer/PayTransferSyncJob.java +++ /dev/null @@ -1,30 +0,0 @@ -package cn.iocoder.yudao.module.pay.job.transfer; - -import cn.hutool.core.util.StrUtil; -import cn.iocoder.yudao.framework.quartz.core.handler.JobHandler; -import cn.iocoder.yudao.framework.tenant.core.job.TenantJob; -import cn.iocoder.yudao.module.pay.service.transfer.PayTransferService; -import org.springframework.stereotype.Component; - -import jakarta.annotation.Resource; - -/** - * 转账订单的同步 Job - * - * 由于转账订单的转账结果,有些渠道是异步通知进行同步的,考虑到异步通知可能会失败(小概率),所以需要定时进行同步。 - * - * @author jason - */ -@Component -public class PayTransferSyncJob implements JobHandler { - - @Resource - private PayTransferService transferService; - - @Override - @TenantJob - public String execute(String param) { - int count = transferService.syncTransfer(); - return StrUtil.format("同步转账订单 {} 个", count); - } -} diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/package-info.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/package-info.java deleted file mode 100644 index 40ab66d..0000000 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/package-info.java +++ /dev/null @@ -1,10 +0,0 @@ -/** - * pay 模块,我们放支付业务,提供业务的支付能力。 - * 例如说:商户、应用、支付、退款等等 - * - * 1. Controller URL:以 /pay/ 开头,避免和其它 Module 冲突 - * 2. DataObject 表名:以 pay_ 开头,方便在数据库中区分 - * - * 注意,由于 Pay 模块和 Trade 模块,容易重名,所以类名都加载 Pay 的前缀~ - */ -package cn.iocoder.yudao.module.pay; diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/app/PayAppService.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/app/PayAppService.java deleted file mode 100644 index d348f53..0000000 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/app/PayAppService.java +++ /dev/null @@ -1,115 +0,0 @@ -package cn.iocoder.yudao.module.pay.service.app; - -import cn.iocoder.yudao.framework.common.exception.ServiceException; -import cn.iocoder.yudao.framework.common.pojo.PageResult; -import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; -import cn.iocoder.yudao.module.pay.controller.admin.app.vo.PayAppCreateReqVO; -import cn.iocoder.yudao.module.pay.controller.admin.app.vo.PayAppPageReqVO; -import cn.iocoder.yudao.module.pay.controller.admin.app.vo.PayAppUpdateReqVO; -import cn.iocoder.yudao.module.pay.dal.dataobject.app.PayAppDO; -import jakarta.validation.Valid; - -import java.util.Collection; -import java.util.List; -import java.util.Map; - -/** - * 支付应用 Service 接口 - * - * @author 芋艿 - */ -public interface PayAppService { - - /** - * 创建支付应用 - * - * @param createReqVO 创建 - * @return 编号 - */ - Long createApp(@Valid PayAppCreateReqVO createReqVO); - - /** - * 更新支付应用 - * - * @param updateReqVO 更新 - */ - void updateApp(@Valid PayAppUpdateReqVO updateReqVO); - - /** - * 修改应用状态 - * - * @param id 应用编号 - * @param status 状态 - */ - void updateAppStatus(Long id, Integer status); - - /** - * 删除支付应用 - * - * @param id 编号 - */ - void deleteApp(Long id); - - /** - * 获得支付应用 - * - * @param id 编号 - * @return 支付应用 - */ - PayAppDO getApp(Long id); - - /** - * 获得支付应用列表 - * - * @param ids 编号 - * @return 支付应用列表 - */ - List getAppList(Collection ids); - - /** - * 获得支付应用列表 - * - * @return 支付应用列表 - */ - List getAppList(); - - /** - * 获得支付应用分页 - * - * @param pageReqVO 分页查询 - * @return 支付应用分页 - */ - PageResult getAppPage(PayAppPageReqVO pageReqVO); - - /** - * 获得指定编号的商户 Map - * - * @param ids 应用编号集合 - * @return 商户 Map - */ - default Map getAppMap(Collection ids) { - List list = getAppList(ids); - return CollectionUtils.convertMap(list, PayAppDO::getId); - } - - /** - * 支付应用的合法性 - *

- * 如果不合法,抛出 {@link ServiceException} 业务异常 - * - * @param id 应用编号 - * @return 应用 - */ - PayAppDO validPayApp(Long id); - - /** - * 支付应用的合法性 - *

- * 如果不合法,抛出 {@link ServiceException} 业务异常 - * - * @param appKey 应用标识 - * @return 应用 - */ - PayAppDO validPayApp(String appKey); - -} diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/app/PayAppServiceImpl.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/app/PayAppServiceImpl.java deleted file mode 100644 index 67fd17b..0000000 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/app/PayAppServiceImpl.java +++ /dev/null @@ -1,167 +0,0 @@ -package cn.iocoder.yudao.module.pay.service.app; - -import cn.hutool.core.collection.CollUtil; -import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; -import cn.iocoder.yudao.framework.common.pojo.PageResult; -import cn.iocoder.yudao.module.pay.controller.admin.app.vo.PayAppCreateReqVO; -import cn.iocoder.yudao.module.pay.controller.admin.app.vo.PayAppPageReqVO; -import cn.iocoder.yudao.module.pay.controller.admin.app.vo.PayAppUpdateReqVO; -import cn.iocoder.yudao.module.pay.convert.app.PayAppConvert; -import cn.iocoder.yudao.module.pay.dal.dataobject.app.PayAppDO; -import cn.iocoder.yudao.module.pay.dal.mysql.app.PayAppMapper; -import cn.iocoder.yudao.module.pay.enums.ErrorCodeConstants; -import cn.iocoder.yudao.module.pay.service.order.PayOrderService; -import cn.iocoder.yudao.module.pay.service.refund.PayRefundService; -import jakarta.annotation.Resource; -import org.springframework.context.annotation.Lazy; -import org.springframework.stereotype.Service; -import org.springframework.validation.annotation.Validated; - -import java.util.Collection; -import java.util.Collections; -import java.util.List; - -import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; -import static cn.iocoder.yudao.module.pay.enums.ErrorCodeConstants.*; - -/** - * 支付应用 Service 实现类 - * - * @author aquan - */ -@Service -@Validated -public class PayAppServiceImpl implements PayAppService { - - @Resource - private PayAppMapper appMapper; - - @Resource - @Lazy // 延迟加载,避免循环依赖报错 - private PayOrderService orderService; - @Resource - @Lazy // 延迟加载,避免循环依赖报错 - private PayRefundService refundService; - - @Override - public Long createApp(PayAppCreateReqVO createReqVO) { - // 验证 appKey 是否重复 - validateAppKeyUnique(null, createReqVO.getAppKey()); - - // 插入 - PayAppDO app = PayAppConvert.INSTANCE.convert(createReqVO); - appMapper.insert(app); - // 返回 - return app.getId(); - } - - @Override - public void updateApp(PayAppUpdateReqVO updateReqVO) { - // 校验存在 - validateAppExists(updateReqVO.getId()); - // 验证 appKey 是否重复 - validateAppKeyUnique(updateReqVO.getId(), updateReqVO.getAppKey()); - - // 更新 - PayAppDO updateObj = PayAppConvert.INSTANCE.convert(updateReqVO); - appMapper.updateById(updateObj); - } - - void validateAppKeyUnique(Long id, String appKey) { - PayAppDO app = appMapper.selectByAppKey(appKey); - if (app == null) { - return; - } - // 如果 id 为空,说明不用比较是否为相同 appKey 的应用 - if (id == null) { - throw exception(APP_KEY_EXISTS); - } - if (!app.getId().equals(id)) { - throw exception(APP_KEY_EXISTS); - } - } - - @Override - public void updateAppStatus(Long id, Integer status) { - // 校验商户存在 - validateAppExists(id); - // 更新状态 - appMapper.updateById(new PayAppDO().setId(id).setStatus(status)); - } - - @Override - public void deleteApp(Long id) { - // 校验存在 - validateAppExists(id); - // 校验关联数据是否存在 - if (orderService.getOrderCountByAppId(id) > 0) { - throw exception(APP_EXIST_ORDER_CANT_DELETE); - } - if (refundService.getRefundCountByAppId(id) > 0) { - throw exception(APP_EXIST_REFUND_CANT_DELETE); - } - - // 删除 - appMapper.deleteById(id); - } - - private void validateAppExists(Long id) { - if (appMapper.selectById(id) == null) { - throw exception(APP_NOT_FOUND); - } - } - - @Override - public PayAppDO getApp(Long id) { - return appMapper.selectById(id); - } - - @Override - public List getAppList(Collection ids) { - if (CollUtil.isEmpty(ids)) { - return Collections.emptyList(); - } - return appMapper.selectBatchIds(ids); - } - - @Override - public List getAppList() { - return appMapper.selectList(); - } - - @Override - public PageResult getAppPage(PayAppPageReqVO pageReqVO) { - return appMapper.selectPage(pageReqVO); - } - - @Override - public PayAppDO validPayApp(Long appId) { - PayAppDO app = appMapper.selectById(appId); - return validatePayApp(app); - } - - @Override - public PayAppDO validPayApp(String appKey) { - PayAppDO app = appMapper.selectByAppKey(appKey); - return validatePayApp(app); - } - - /** - * 校验支付应用实体的有效性:存在 + 开启 - * - * @param app 待校验的支付应用实体 - * @return 校验通过的支付应用实体 - */ - private PayAppDO validatePayApp(PayAppDO app) { - // 校验是否存在 - if (app == null) { - throw exception(ErrorCodeConstants.APP_NOT_FOUND); - } - // 校验是否禁用 - if (CommonStatusEnum.isDisable(app.getStatus())) { - throw exception(ErrorCodeConstants.APP_IS_DISABLE); - } - return app; - } - -} 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 deleted file mode 100644 index c397986..0000000 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/channel/PayChannelService.java +++ /dev/null @@ -1,104 +0,0 @@ -package cn.iocoder.yudao.module.pay.service.channel; - -import cn.iocoder.yudao.framework.common.exception.ServiceException; -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; - -import jakarta.validation.Valid; -import java.util.Collection; -import java.util.List; - -/** - * 支付渠道 Service 接口 - * - * @author aquan - */ -public interface PayChannelService { - - /** - * 创建支付渠道 - * - * @param createReqVO 创建信息 - * @return 编号 - */ - Long createChannel(@Valid PayChannelCreateReqVO createReqVO); - - /** - * 更新支付渠道 - * - * @param updateReqVO 更新信息 - */ - void updateChannel(@Valid PayChannelUpdateReqVO updateReqVO); - - /** - * 删除支付渠道 - * - * @param id 编号 - */ - void deleteChannel(Long id); - - /** - * 获得支付渠道 - * - * @param id 编号 - * @return 支付渠道 - */ - PayChannelDO getChannel(Long id); - - /** - * 根据支付应用 ID 集合,获得支付渠道列表 - * - * @param appIds 应用编号集合 - * @return 支付渠道列表 - */ - List getChannelListByAppIds(Collection appIds); - - /** - * 根据条件获取渠道 - * - * @param appId 应用编号 - * @param code 渠道编码 - * @return 数量 - */ - PayChannelDO getChannelByAppIdAndCode(Long appId, String code); - - /** - * 支付渠道的合法性 - * - * 如果不合法,抛出 {@link ServiceException} 业务异常 - * - * @param id 渠道编号 - * @return 渠道信息 - */ - PayChannelDO validPayChannel(Long id); - - /** - * 支付渠道的合法性 - * - * 如果不合法,抛出 {@link ServiceException} 业务异常 - * - * @param appId 应用编号 - * @param code 支付渠道 - * @return 渠道信息 - */ - PayChannelDO validPayChannel(Long appId, String code); - - /** - * 获得指定应用的开启的渠道列表 - * - * @param appId 应用编号 - * @return 渠道列表 - */ - List getEnableChannelList(Long appId); - - /** - * 获得指定编号的支付客户端 - * - * @param id 编号 - * @return 支付客户端 - */ - PayClient getPayClient(Long id); - -} 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 deleted file mode 100644 index 56ff5e5..0000000 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/channel/PayChannelServiceImpl.java +++ /dev/null @@ -1,165 +0,0 @@ -package cn.iocoder.yudao.module.pay.service.channel; - -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.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.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; -import org.springframework.stereotype.Service; -import org.springframework.validation.annotation.Validated; - -import java.util.Collection; -import java.util.List; - -import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; -import static cn.iocoder.yudao.module.pay.enums.ErrorCodeConstants.*; - -/** - * 支付渠道 Service 实现类 - * - * @author aquan - */ -@Service -@Slf4j -@Validated -public class PayChannelServiceImpl implements PayChannelService { - - @Resource - private PayClientFactory payClientFactory; - - @Resource - private PayChannelMapper payChannelMapper; - - @Resource - private Validator validator; - - @Override - public Long createChannel(PayChannelCreateReqVO reqVO) { - // 断言是否有重复的 - PayChannelDO dbChannel = getChannelByAppIdAndCode(reqVO.getAppId(), reqVO.getCode()); - if (dbChannel != null) { - throw exception(CHANNEL_EXIST_SAME_CHANNEL_ERROR); - } - - // 新增渠道 - PayChannelDO channel = PayChannelConvert.INSTANCE.convert(reqVO) - .setConfig(parseConfig(reqVO.getCode(), reqVO.getConfig())); - payChannelMapper.insert(channel); - return channel.getId(); - } - - @Override - public void updateChannel(PayChannelUpdateReqVO updateReqVO) { - // 校验存在 - PayChannelDO dbChannel = validateChannelExists(updateReqVO.getId()); - - // 更新 - PayChannelDO channel = PayChannelConvert.INSTANCE.convert(updateReqVO) - .setConfig(parseConfig(dbChannel.getCode(), updateReqVO.getConfig())); - payChannelMapper.updateById(channel); - } - - /** - * 解析并校验配置 - * - * @param code 渠道编码 - * @param configStr 配置 - * @return 支付配置 - */ - private PayClientConfig parseConfig(String code, String configStr) { - // 解析配置 - Class payClass = PayChannelEnum.isAlipay(configStr) ? AlipayPayClientConfig.class - : PayChannelEnum.isWeixin(configStr) ? WxPayClientConfig.class - : NonePayClientConfig.class; - if (ObjectUtil.isNull(payClass)) { - throw exception(CHANNEL_NOT_FOUND); - } - PayClientConfig config = JsonUtils.parseObject2(configStr, payClass); - Assert.notNull(config); - - // 验证参数 - config.validate(validator); - return config; - } - - @Override - public void deleteChannel(Long id) { - // 校验存在 - validateChannelExists(id); - - // 删除 - payChannelMapper.deleteById(id); - } - - private PayChannelDO validateChannelExists(Long id) { - PayChannelDO channel = payChannelMapper.selectById(id); - if (channel == null) { - throw exception(CHANNEL_NOT_FOUND); - } - return channel; - } - - @Override - public PayChannelDO getChannel(Long id) { - return payChannelMapper.selectById(id); - } - - @Override - public List getChannelListByAppIds(Collection appIds) { - return payChannelMapper.selectListByAppIds(appIds); - } - - @Override - public PayChannelDO getChannelByAppIdAndCode(Long appId, String code) { - return payChannelMapper.selectByAppIdAndCode(appId, code); - } - - @Override - public PayChannelDO validPayChannel(Long id) { - PayChannelDO channel = payChannelMapper.selectById(id); - validPayChannel(channel); - return channel; - } - - @Override - public PayChannelDO validPayChannel(Long appId, String code) { - PayChannelDO channel = payChannelMapper.selectByAppIdAndCode(appId, code); - validPayChannel(channel); - return channel; - } - - private void validPayChannel(PayChannelDO channel) { - if (channel == null) { - throw exception(CHANNEL_NOT_FOUND); - } - if (CommonStatusEnum.DISABLE.getStatus().equals(channel.getStatus())) { - throw exception(CHANNEL_IS_DISABLE); - } - } - - @Override - public List getEnableChannelList(Long appId) { - return payChannelMapper.selectListByAppId(appId, CommonStatusEnum.ENABLE.getStatus()); - } - - @Override - public PayClient getPayClient(Long id) { - PayChannelDO channel = validPayChannel(id); - return payClientFactory.createOrUpdatePayClient(id, channel.getCode(), channel.getConfig()); - } - -} diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/demo/PayDemoOrderService.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/demo/PayDemoOrderService.java deleted file mode 100644 index 18de7b9..0000000 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/demo/PayDemoOrderService.java +++ /dev/null @@ -1,67 +0,0 @@ -package cn.iocoder.yudao.module.pay.service.demo; - -import cn.iocoder.yudao.framework.common.pojo.PageParam; -import cn.iocoder.yudao.framework.common.pojo.PageResult; -import cn.iocoder.yudao.module.pay.controller.admin.demo.vo.order.PayDemoOrderCreateReqVO; -import cn.iocoder.yudao.module.pay.dal.dataobject.demo.PayDemoOrderDO; - -import jakarta.validation.Valid; - -/** - * 示例订单 Service 接口 - * - * @author 芋道源码 - */ -public interface PayDemoOrderService { - - /** - * 创建示例订单 - * - * @param userId 用户编号 - * @param createReqVO 创建信息 - * @return 编号 - */ - Long createDemoOrder(Long userId, @Valid PayDemoOrderCreateReqVO createReqVO); - - /** - * 获得示例订单 - * - * @param id 编号 - * @return 示例订单 - */ - PayDemoOrderDO getDemoOrder(Long id); - - /** - * 获得示例订单分页 - * - * @param pageReqVO 分页查询 - * @return 示例订单分页 - */ - PageResult getDemoOrderPage(PageParam pageReqVO); - - /** - * 更新示例订单为已支付 - * - * @param id 编号 - * @param payOrderId 支付订单号 - */ - void updateDemoOrderPaid(Long id, Long payOrderId); - - /** - * 发起示例订单的退款 - * - * @param id 编号 - * @param userIp 用户编号 - */ - void refundDemoOrder(Long id, String userIp); - - /** - * 更新示例订单为已退款 - * - * @param id 编号 - * @param refundId 退款编号 - * @param payRefundId 退款订单号 - */ - void updateDemoOrderRefunded(Long id, String refundId, Long payRefundId); - -} diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/demo/PayDemoOrderServiceImpl.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/demo/PayDemoOrderServiceImpl.java deleted file mode 100644 index 76571f3..0000000 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/demo/PayDemoOrderServiceImpl.java +++ /dev/null @@ -1,265 +0,0 @@ -package cn.iocoder.yudao.module.pay.service.demo; - -import cn.hutool.core.lang.Assert; -import cn.hutool.core.util.ObjUtil; -import cn.hutool.core.util.ObjectUtil; -import cn.iocoder.yudao.framework.common.pojo.PageParam; -import cn.iocoder.yudao.framework.common.pojo.PageResult; -import cn.iocoder.yudao.module.pay.api.order.PayOrderApi; -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.api.refund.PayRefundApi; -import cn.iocoder.yudao.module.pay.api.refund.dto.PayRefundCreateReqDTO; -import cn.iocoder.yudao.module.pay.api.refund.dto.PayRefundRespDTO; -import cn.iocoder.yudao.module.pay.controller.admin.demo.vo.order.PayDemoOrderCreateReqVO; -import cn.iocoder.yudao.module.pay.dal.dataobject.demo.PayDemoOrderDO; -import cn.iocoder.yudao.module.pay.dal.mysql.demo.PayDemoOrderMapper; -import cn.iocoder.yudao.module.pay.enums.order.PayOrderStatusEnum; -import cn.iocoder.yudao.module.pay.enums.refund.PayRefundStatusEnum; -import jakarta.annotation.Resource; -import lombok.extern.slf4j.Slf4j; -import org.springframework.stereotype.Service; -import org.springframework.validation.annotation.Validated; - -import java.time.Duration; -import java.time.LocalDateTime; -import java.util.HashMap; -import java.util.Map; - -import static cn.hutool.core.util.ObjectUtil.notEqual; -import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; -import static cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.addTime; -import static cn.iocoder.yudao.framework.common.util.json.JsonUtils.toJsonString; -import static cn.iocoder.yudao.framework.common.util.servlet.ServletUtils.getClientIP; -import static cn.iocoder.yudao.module.pay.enums.ErrorCodeConstants.*; - -/** - * 示例订单 Service 实现类 - * - * @author 芋道源码 - */ -@Service -@Validated -@Slf4j -public class PayDemoOrderServiceImpl implements PayDemoOrderService { - - /** - * 接入的支付应用标识 - * - * 从 [支付管理 -> 应用信息] 里添加 - */ - private static final String PAY_APP_KEY = "demo"; - - /** - * 商品信息 Map - * - * key:商品编号 - * value:[商品名、商品价格] - */ - private final Map spuNames = new HashMap<>(); - - @Resource - private PayOrderApi payOrderApi; - @Resource - private PayRefundApi payRefundApi; - - @Resource - private PayDemoOrderMapper payDemoOrderMapper; - - public PayDemoOrderServiceImpl() { - spuNames.put(1L, new Object[]{"华为手机", 1}); - spuNames.put(2L, new Object[]{"小米电视", 10}); - spuNames.put(3L, new Object[]{"苹果手表", 100}); - spuNames.put(4L, new Object[]{"华硕笔记本", 1000}); - spuNames.put(5L, new Object[]{"蔚来汽车", 200000}); - } - - @Override - public Long createDemoOrder(Long userId, PayDemoOrderCreateReqVO createReqVO) { - // 1.1 获得商品 - Object[] spu = spuNames.get(createReqVO.getSpuId()); - Assert.notNull(spu, "商品({}) 不存在", createReqVO.getSpuId()); - String spuName = (String) spu[0]; - Integer price = (Integer) spu[1]; - // 1.2 插入 demo 订单 - PayDemoOrderDO demoOrder = new PayDemoOrderDO().setUserId(userId) - .setSpuId(createReqVO.getSpuId()).setSpuName(spuName) - .setPrice(price).setPayStatus(false).setRefundPrice(0); - payDemoOrderMapper.insert(demoOrder); - - // 2.1 创建支付单 - Long payOrderId = payOrderApi.createOrder(new PayOrderCreateReqDTO() - .setAppKey(PAY_APP_KEY).setUserIp(getClientIP()) // 支付应用 - .setMerchantOrderId(demoOrder.getId().toString()) // 业务的订单编号 - .setSubject(spuName).setBody("").setPrice(price) // 价格信息 - .setExpireTime(addTime(Duration.ofHours(2L)))); // 支付的过期时间 - // 2.2 更新支付单到 demo 订单 - payDemoOrderMapper.updateById(new PayDemoOrderDO().setId(demoOrder.getId()) - .setPayOrderId(payOrderId)); - return demoOrder.getId(); - } - - @Override - public PayDemoOrderDO getDemoOrder(Long id) { - return payDemoOrderMapper.selectById(id); - } - - @Override - public PageResult getDemoOrderPage(PageParam pageReqVO) { - return payDemoOrderMapper.selectPage(pageReqVO); - } - - @Override - public void updateDemoOrderPaid(Long id, Long payOrderId) { - // 1.1 校验订单是否存在 - PayDemoOrderDO order = payDemoOrderMapper.selectById(id); - if (order == null) { - log.error("[updateDemoOrderPaid][order({}) payOrder({}) 不存在订单,请进行处理!]", id, payOrderId); - throw exception(DEMO_ORDER_NOT_FOUND); - } - // 1.2 校验订单已支付 - if (order.getPayStatus()) { - // 特殊:支付单号相同,直接返回,说明重复回调 - if (ObjectUtil.equals(order.getPayOrderId(), payOrderId)) { - log.warn("[updateDemoOrderPaid][order({}) 已支付,且支付单号相同({}),直接返回]", order, payOrderId); - return; - } - // 异常:支付单号不同,说明支付单号错误 - log.error("[updateDemoOrderPaid][order({}) 支付单不匹配({}),请进行处理!]", - order, payOrderId); - throw exception(DEMO_ORDER_UPDATE_PAID_FAIL_PAY_ORDER_ID_ERROR); - } - - // 2. 校验支付订单的合法性 - PayOrderRespDTO payOrder = validatePayOrderPaid(order, payOrderId); - - // 3. 更新 PayDemoOrderDO 状态为已支付 - int updateCount = payDemoOrderMapper.updateByIdAndPayed(id, false, - new PayDemoOrderDO().setPayStatus(true).setPayTime(LocalDateTime.now()) - .setPayChannelCode(payOrder.getChannelCode())); - if (updateCount == 0) { - throw exception(DEMO_ORDER_UPDATE_PAID_STATUS_NOT_UNPAID); - } - } - - /** - * 校验支付订单的合法性 - * - * @param order 交易订单 - * @param payOrderId 支付订单编号 - * @return 支付订单 - */ - private PayOrderRespDTO validatePayOrderPaid(PayDemoOrderDO order, Long payOrderId) { - // 1. 校验支付单是否存在 - PayOrderRespDTO payOrder = payOrderApi.getOrder(payOrderId); - if (payOrder == null) { - log.error("[validatePayOrderPaid][order({}) payOrder({}) 不存在,请进行处理!]", order.getId(), payOrderId); - throw exception(PAY_ORDER_NOT_FOUND); - } - // 2.1 校验支付单已支付 - if (!PayOrderStatusEnum.isSuccess(payOrder.getStatus())) { - log.error("[validatePayOrderPaid][order({}) payOrder({}) 未支付,请进行处理!payOrder 数据是:{}]", - order.getId(), payOrderId, toJsonString(payOrder)); - throw exception(DEMO_ORDER_UPDATE_PAID_FAIL_PAY_ORDER_STATUS_NOT_SUCCESS); - } - // 2.1 校验支付金额一致 - if (notEqual(payOrder.getPrice(), order.getPrice())) { - log.error("[validatePayOrderPaid][order({}) payOrder({}) 支付金额不匹配,请进行处理!order 数据是:{},payOrder 数据是:{}]", - order.getId(), payOrderId, toJsonString(order), toJsonString(payOrder)); - throw exception(DEMO_ORDER_UPDATE_PAID_FAIL_PAY_PRICE_NOT_MATCH); - } - // 2.2 校验支付订单匹配(二次) - if (notEqual(payOrder.getMerchantOrderId(), order.getId().toString())) { - log.error("[validatePayOrderPaid][order({}) 支付单不匹配({}),请进行处理!payOrder 数据是:{}]", - order.getId(), payOrderId, toJsonString(payOrder)); - throw exception(DEMO_ORDER_UPDATE_PAID_FAIL_PAY_ORDER_ID_ERROR); - } - return payOrder; - } - - @Override - public void refundDemoOrder(Long id, String userIp) { - // 1. 校验订单是否可以退款 - PayDemoOrderDO order = validateDemoOrderCanRefund(id); - - // 2.1 生成退款单号 - // 一般来说,用户发起退款的时候,都会单独插入一个售后维权表,然后使用该表的 id 作为 refundId - // 这里我们是个简单的 demo,所以没有售后维权表,直接使用订单 id + "-refund" 来演示 - String refundId = order.getId() + "-refund"; - // 2.2 创建退款单 - Long payRefundId = payRefundApi.createRefund(new PayRefundCreateReqDTO() - .setAppKey(PAY_APP_KEY).setUserIp(getClientIP()) // 支付应用 - .setMerchantOrderId(String.valueOf(order.getId())) // 支付单号 - .setMerchantRefundId(refundId) - .setReason("想退钱").setPrice(order.getPrice()));// 价格信息 - // 2.3 更新退款单到 demo 订单 - payDemoOrderMapper.updateById(new PayDemoOrderDO().setId(id) - .setPayRefundId(payRefundId).setRefundPrice(order.getPrice())); - } - - private PayDemoOrderDO validateDemoOrderCanRefund(Long id) { - // 校验订单是否存在 - PayDemoOrderDO order = payDemoOrderMapper.selectById(id); - if (order == null) { - throw exception(DEMO_ORDER_NOT_FOUND); - } - // 校验订单是否支付 - if (!order.getPayStatus()) { - throw exception(DEMO_ORDER_REFUND_FAIL_NOT_PAID); - } - // 校验订单是否已退款 - if (order.getPayRefundId() != null) { - throw exception(DEMO_ORDER_REFUND_FAIL_REFUNDED); - } - return order; - } - - @Override - public void updateDemoOrderRefunded(Long id, String refundId, Long payRefundId) { - // 1. 校验并获得退款订单(可退款) - PayRefundRespDTO payRefund = validateDemoOrderCanRefunded(id, refundId, payRefundId); - // 2.2 更新退款单到 demo 订单 - payDemoOrderMapper.updateById(new PayDemoOrderDO().setId(id) - .setRefundTime(payRefund.getSuccessTime())); - } - - private PayRefundRespDTO validateDemoOrderCanRefunded(Long id, String refundId, Long payRefundId) { - // 1.1 校验示例订单 - // 一般来说,这里应该用 refundId 来查询退款单,然后再校验订单是否匹配 - // 这里我们是个简单的 demo,所以没有售后维权表,直接使用订单 id 来查询订单 - PayDemoOrderDO order = payDemoOrderMapper.selectById(id); - if (order == null) { - throw exception(DEMO_ORDER_NOT_FOUND); - } - // 1.2 校验退款订单匹配 - if (ObjUtil.notEqual(order.getPayRefundId(), payRefundId)) { - log.error("[validateDemoOrderCanRefunded][order({}) 退款单不匹配({}),请进行处理!order 数据是:{}]", - id, payRefundId, toJsonString(order)); - throw exception(DEMO_ORDER_REFUND_FAIL_REFUND_ORDER_ID_ERROR); - } - - // 2.1 校验退款订单 - PayRefundRespDTO payRefund = payRefundApi.getRefund(payRefundId); - if (payRefund == null) { - throw exception(DEMO_ORDER_REFUND_FAIL_REFUND_NOT_FOUND); - } - // 2.2 必须是退款成功状态 - if (!PayRefundStatusEnum.isSuccess(payRefund.getStatus())) { - throw exception(DEMO_ORDER_REFUND_FAIL_REFUND_NOT_SUCCESS); - } - // 2.3 校验退款金额一致 - if (notEqual(payRefund.getRefundPrice(), order.getPrice())) { - log.error("[validateDemoOrderCanRefunded][order({}) payRefund({}) 退款金额不匹配,请进行处理!order 数据是:{},payRefund 数据是:{}]", - id, payRefundId, toJsonString(order), toJsonString(payRefund)); - throw exception(DEMO_ORDER_REFUND_FAIL_REFUND_PRICE_NOT_MATCH); - } - // 2.4 校验退款订单匹配(二次) - if (notEqual(payRefund.getMerchantRefundId(), id.toString() + "-refund")) { - log.error("[validateDemoOrderCanRefunded][order({}) 退款单不匹配({}),请进行处理!payRefund 数据是:{}]", - id, payRefundId, toJsonString(payRefund)); - throw exception(DEMO_ORDER_REFUND_FAIL_REFUND_ORDER_ID_ERROR); - } - return payRefund; - } - -} diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/demo/PayDemoWithdrawService.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/demo/PayDemoWithdrawService.java deleted file mode 100644 index e8a5980..0000000 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/demo/PayDemoWithdrawService.java +++ /dev/null @@ -1,48 +0,0 @@ -package cn.iocoder.yudao.module.pay.service.demo; - -import cn.iocoder.yudao.framework.common.pojo.PageParam; -import cn.iocoder.yudao.framework.common.pojo.PageResult; -import cn.iocoder.yudao.module.pay.controller.admin.demo.vo.withdraw.PayDemoWithdrawCreateReqVO; -import cn.iocoder.yudao.module.pay.dal.dataobject.demo.PayDemoWithdrawDO; - -import jakarta.validation.Valid; - -/** - * 示例提现单 Service 接口 - * - * @author jason - */ -public interface PayDemoWithdrawService { - - /** - * 创建示例提现单 - * - * @param createReqVO 创建信息 - * @return 编号 - */ - Long createDemoWithdraw(@Valid PayDemoWithdrawCreateReqVO createReqVO); - - /** - * 提现单转账 - * - * @param id 提现单编号 - * @return 转账编号 - */ - Long transferDemoWithdraw(Long id); - - /** - * 获得示例提现单分页 - * - * @param pageVO 分页查询参数 - */ - PageResult getDemoWithdrawPage(PageParam pageVO); - - /** - * 更新示例提现单的状态 - * - * @param id 编号 - * @param payTransferId 转账单编号 - */ - void updateDemoWithdrawTransferred(Long id, Long payTransferId); - -} diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/demo/PayDemoWithdrawServiceImpl.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/demo/PayDemoWithdrawServiceImpl.java deleted file mode 100644 index b1a3b91..0000000 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/demo/PayDemoWithdrawServiceImpl.java +++ /dev/null @@ -1,195 +0,0 @@ -package cn.iocoder.yudao.module.pay.service.demo; - -import cn.hutool.core.lang.Assert; -import cn.hutool.core.util.ObjectUtil; -import cn.iocoder.yudao.framework.common.pojo.PageParam; -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.module.pay.api.transfer.PayTransferApi; -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; -import cn.iocoder.yudao.module.pay.controller.admin.demo.vo.withdraw.PayDemoWithdrawCreateReqVO; -import cn.iocoder.yudao.module.pay.dal.dataobject.demo.PayDemoWithdrawDO; -import cn.iocoder.yudao.module.pay.dal.mysql.demo.PayDemoWithdrawMapper; -import cn.iocoder.yudao.module.pay.enums.PayChannelEnum; -import cn.iocoder.yudao.module.pay.enums.demo.PayDemoWithdrawStatusEnum; -import cn.iocoder.yudao.module.pay.enums.demo.PayDemoWithdrawTypeEnum; -import cn.iocoder.yudao.module.pay.enums.transfer.PayTransferStatusEnum; -import jakarta.annotation.Resource; -import jakarta.validation.Valid; -import lombok.extern.slf4j.Slf4j; -import org.springframework.stereotype.Service; -import org.springframework.validation.annotation.Validated; - -import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; -import static cn.iocoder.yudao.framework.common.util.servlet.ServletUtils.getClientIP; -import static cn.iocoder.yudao.module.pay.enums.ErrorCodeConstants.*; - -/** - * 示例转账业务 Service 实现类 - * - * @author jason - */ -@Service -@Validated -@Slf4j -public class PayDemoWithdrawServiceImpl implements PayDemoWithdrawService { - - /** - * 接入的支付应用标识 - * - * 从 [支付管理 -> 应用信息] 里添加 - */ - private static final String PAY_APP_KEY = "demo"; - - @Resource - private PayDemoWithdrawMapper demoTransferMapper; - - @Resource - private PayTransferApi payTransferApi; - - @Override - public Long createDemoWithdraw(@Valid PayDemoWithdrawCreateReqVO reqVO) { - PayDemoWithdrawDO withdraw = BeanUtils.toBean(reqVO, PayDemoWithdrawDO.class) - .setTransferChannelCode(getTransferChannelCode(reqVO.getType())) - .setStatus(PayDemoWithdrawStatusEnum.WAITING.getStatus()); - demoTransferMapper.insert(withdraw); - return withdraw.getId(); - } - - @Override - public Long transferDemoWithdraw(Long id) { - // 1.1 校验提现单 - PayDemoWithdrawDO withdraw = validateDemoWithdrawCanTransfer(id); - // 1.2 特殊:如果是转账失败的情况,需要充值下 - if (PayDemoWithdrawStatusEnum.isClosed(withdraw.getStatus())) { - int updateCount = demoTransferMapper.updateByIdAndStatus(withdraw.getId(), withdraw.getStatus(), - new PayDemoWithdrawDO().setStatus(PayDemoWithdrawStatusEnum.WAITING.getStatus()).setTransferErrorMsg("")); - if (updateCount == 0) { - throw exception(DEMO_WITHDRAW_TRANSFER_FAIL_STATUS_NOT_WAITING_OR_CLOSED); - } - withdraw.setStatus(PayDemoWithdrawStatusEnum.WAITING.getStatus()); - } - - // 2.1 创建支付单 - PayTransferCreateReqDTO transferReqDTO = new PayTransferCreateReqDTO() - .setAppKey(PAY_APP_KEY).setChannelCode(withdraw.getTransferChannelCode()).setUserIp(getClientIP()) // 支付应用 - .setMerchantTransferId(String.valueOf(withdraw.getId())) // 业务的订单编号 - .setSubject(withdraw.getSubject()).setPrice(withdraw.getPrice()) // 价格信息 - .setUserAccount(withdraw.getUserAccount()).setUserName(withdraw.getUserName()); // 收款信息 - if (ObjectUtil.equal(withdraw.getType(), PayDemoWithdrawTypeEnum.WECHAT.getType())) { - // 注意:微信很特殊!提现需要写明用途!!! - transferReqDTO.setChannelExtras(PayTransferCreateReqDTO.buildWeiXinChannelExtra1000( - "测试活动", "测试奖励")); - } - PayTransferCreateRespDTO transferRespDTO = payTransferApi.createTransfer(transferReqDTO); - - // 2.2 更新转账单到 demo 示例提现单,并将状态更新为转账中 - demoTransferMapper.updateByIdAndStatus(withdraw.getId(), withdraw.getStatus(), - new PayDemoWithdrawDO().setPayTransferId(transferRespDTO.getId())); - return transferRespDTO.getId(); - } - - private PayDemoWithdrawDO validateDemoWithdrawCanTransfer(Long id) { - // 校验存在 - PayDemoWithdrawDO withdraw = demoTransferMapper.selectById(id); - if (withdraw == null) { - throw exception(DEMO_WITHDRAW_NOT_FOUND); - } - // 校验状态,只有等待中或转账失败的订单,才能发起转账 - if (!PayDemoWithdrawStatusEnum.isWaiting(withdraw.getStatus()) - && !PayDemoWithdrawStatusEnum.isClosed(withdraw.getStatus())) { - throw exception(DEMO_WITHDRAW_TRANSFER_FAIL_STATUS_NOT_WAITING_OR_CLOSED); - } - return withdraw; - } - - private String getTransferChannelCode(Integer type) { - if (ObjectUtil.equal(type, PayDemoWithdrawTypeEnum.ALIPAY.getType())) { - return PayChannelEnum.ALIPAY_PC.getCode(); - } - if (ObjectUtil.equal(type, PayDemoWithdrawTypeEnum.WECHAT.getType())) { - return PayChannelEnum.WX_LITE.getCode(); - } - if (ObjectUtil.equal(type, PayDemoWithdrawTypeEnum.WALLET.getType())) { - return PayChannelEnum.WALLET.getCode(); - } - throw new IllegalArgumentException("未知提现方式:" + type); - } - - @Override - public PageResult getDemoWithdrawPage(PageParam pageVO) { - return demoTransferMapper.selectPage(pageVO); - } - - @Override - public void updateDemoWithdrawTransferred(Long id, Long payTransferId) { - // 1.1 校验转账单是否存在 - PayDemoWithdrawDO withdraw = demoTransferMapper.selectById(id); - if (withdraw == null) { - log.error("[updateDemoWithdrawStatus][withdraw({}) payOrder({}) 不存在提现单,请进行处理!]", id, payTransferId); - throw exception(DEMO_WITHDRAW_NOT_FOUND); - } - // 1.2 校验转账单已成结束(成功或失败) - if (PayDemoWithdrawStatusEnum.isSuccess(withdraw.getStatus()) - || PayDemoWithdrawStatusEnum.isClosed(withdraw.getStatus())) { - // 特殊:转账单编号相同,直接返回,说明重复回调 - if (ObjectUtil.equal(withdraw.getPayTransferId(), payTransferId)) { - log.warn("[updateDemoWithdrawStatus][withdraw({}) 已结束,且转账单编号相同({}),直接返回]", withdraw, payTransferId); - return; - } - // 异常:转账单编号不同,说明转账单编号错误 - log.error("[updateDemoWithdrawStatus][withdraw({}) 转账单不匹配({}),请进行处理!]", withdraw, payTransferId); - throw exception(DEMO_WITHDRAW_UPDATE_STATUS_FAIL_PAY_TRANSFER_ID_ERROR); - } - - // 2. 校验提现单的合法性 - PayTransferRespDTO payTransfer = validateDemoTransferStatusCanUpdate(withdraw, payTransferId); - - // 3. 更新示例订单状态 - Integer newStatus = PayTransferStatusEnum.isSuccess(payTransfer.getStatus()) ? PayDemoWithdrawStatusEnum.SUCCESS.getStatus() : - PayTransferStatusEnum.isClosed(payTransfer.getStatus()) ? PayDemoWithdrawStatusEnum.CLOSED.getStatus() : null; - Assert.notNull(newStatus, "转账单状态({}) 不合法", payTransfer.getStatus()); - demoTransferMapper.updateByIdAndStatus(withdraw.getId(), withdraw.getStatus(), - new PayDemoWithdrawDO().setStatus(newStatus).setTransferTime(payTransfer.getSuccessTime()) - .setTransferErrorMsg(payTransfer.getChannelErrorMsg())); - } - - private PayTransferRespDTO validateDemoTransferStatusCanUpdate(PayDemoWithdrawDO withdraw, Long payTransferId) { - // 1. 校验转账单是否存在 - PayTransferRespDTO payTransfer = payTransferApi.getTransfer(payTransferId); - if (payTransfer == null) { - log.error("[validateDemoTransferStatusCanUpdate][withdraw({}) payTransfer({}) 不存在,请进行处理!]", withdraw.getId(), payTransferId); - throw exception(PAY_TRANSFER_NOT_FOUND); - } - - // 2.1 校验转账单已成功 - if (!PayTransferStatusEnum.isSuccessOrClosed(payTransfer.getStatus())) { - log.error("[validateDemoTransferStatusCanUpdate][withdraw({}) payTransfer({}) 未结束,请进行处理!payTransfer 数据是:{}]", - withdraw.getId(), payTransferId, JsonUtils.toJsonString(payTransfer)); - throw exception(DEMO_WITHDRAW_UPDATE_STATUS_FAIL_PAY_TRANSFER_STATUS_NOT_SUCCESS_OR_CLOSED); - } - // 2.2 校验转账金额一致 - if (ObjectUtil.notEqual(payTransfer.getPrice(), withdraw.getPrice())) { - log.error("[validateDemoTransferStatusCanUpdate][withdraw({}) payTransfer({}) 转账金额不匹配,请进行处理!withdraw 数据是:{},payTransfer 数据是:{}]", - withdraw.getId(), payTransferId, JsonUtils.toJsonString(withdraw), JsonUtils.toJsonString(payTransfer)); - throw exception(DEMO_WITHDRAW_UPDATE_STATUS_FAIL_PAY_PRICE_NOT_MATCH); - } - // 2.3 校验转账订单匹配(二次) - if (ObjectUtil.notEqual(payTransfer.getMerchantTransferId(), withdraw.getId().toString())) { - log.error("[validateDemoTransferStatusCanUpdate][withdraw({}) 转账单不匹配({}),请进行处理!payTransfer 数据是:{}]", - withdraw.getId(), payTransferId, JsonUtils.toJsonString(payTransfer)); - throw exception(DEMO_WITHDRAW_UPDATE_STATUS_FAIL_PAY_MERCHANT_EXISTS); - } - // 2.4 校验转账渠道一致 - if (ObjectUtil.notEqual(payTransfer.getChannelCode(), withdraw.getTransferChannelCode())) { - log.error("[validateDemoTransferStatusCanUpdate][withdraw({}) payTransfer({}) 转账渠道不匹配,请进行处理!withdraw 数据是:{},payTransfer 数据是:{}]", - withdraw.getId(), payTransferId, JsonUtils.toJsonString(withdraw), JsonUtils.toJsonString(payTransfer)); - throw exception(DEMO_WITHDRAW_UPDATE_STATUS_FAIL_PAY_CHANNEL_NOT_MATCH); - } - return payTransfer; - } - -} diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/notify/PayNotifyService.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/notify/PayNotifyService.java deleted file mode 100644 index ed93997..0000000 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/notify/PayNotifyService.java +++ /dev/null @@ -1,57 +0,0 @@ -package cn.iocoder.yudao.module.pay.service.notify; - -import cn.iocoder.yudao.framework.common.pojo.PageResult; -import cn.iocoder.yudao.module.pay.controller.admin.notify.vo.PayNotifyTaskPageReqVO; -import cn.iocoder.yudao.module.pay.dal.dataobject.notify.PayNotifyLogDO; -import cn.iocoder.yudao.module.pay.dal.dataobject.notify.PayNotifyTaskDO; - -import java.util.List; - -/** - * 回调通知 Service 接口 - * - * @author 芋道源码 - */ -public interface PayNotifyService { - - /** - * 创建回调通知任务 - * - * @param type 类型 - * @param dataId 数据编号 - */ - void createPayNotifyTask(Integer type, Long dataId); - - /** - * 执行回调通知 - * - * 注意,该方法提供给定时任务调用。目前是 yudao-server 进行调用 - * @return 通知数量 - */ - int executeNotify() throws InterruptedException; - - /** - * 获得回调通知 - * - * @param id 编号 - * @return 回调通知 - */ - PayNotifyTaskDO getNotifyTask(Long id); - - /** - * 获得回调通知分页 - * - * @param pageReqVO 分页查询 - * @return 回调通知分页 - */ - PageResult getNotifyTaskPage(PayNotifyTaskPageReqVO pageReqVO); - - /** - * 获得回调日志列表 - * - * @param taskId 任务编号 - * @return 日志列表 - */ - List getNotifyLogList(Long taskId); - -} diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/notify/PayNotifyServiceImpl.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/notify/PayNotifyServiceImpl.java deleted file mode 100644 index 0956087..0000000 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/notify/PayNotifyServiceImpl.java +++ /dev/null @@ -1,323 +0,0 @@ -package cn.iocoder.yudao.module.pay.service.notify; - -import cn.hutool.core.collection.CollUtil; -import cn.hutool.core.exceptions.ExceptionUtil; -import cn.hutool.core.util.ObjectUtil; -import cn.hutool.extra.spring.SpringUtil; -import cn.hutool.http.HttpResponse; -import cn.hutool.http.HttpUtil; -import cn.iocoder.yudao.framework.common.pojo.CommonResult; -import cn.iocoder.yudao.framework.common.pojo.PageResult; -import cn.iocoder.yudao.framework.common.util.date.DateUtils; -import cn.iocoder.yudao.framework.common.util.json.JsonUtils; -import cn.iocoder.yudao.framework.tenant.core.util.TenantUtils; -import cn.iocoder.yudao.module.pay.api.notify.dto.PayOrderNotifyReqDTO; -import cn.iocoder.yudao.module.pay.api.notify.dto.PayRefundNotifyReqDTO; -import cn.iocoder.yudao.module.pay.api.notify.dto.PayTransferNotifyReqDTO; -import cn.iocoder.yudao.module.pay.controller.admin.notify.vo.PayNotifyTaskPageReqVO; -import cn.iocoder.yudao.module.pay.dal.dataobject.notify.PayNotifyLogDO; -import cn.iocoder.yudao.module.pay.dal.dataobject.notify.PayNotifyTaskDO; -import cn.iocoder.yudao.module.pay.dal.dataobject.order.PayOrderDO; -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.mysql.notify.PayNotifyLogMapper; -import cn.iocoder.yudao.module.pay.dal.mysql.notify.PayNotifyTaskMapper; -import cn.iocoder.yudao.module.pay.dal.redis.notify.PayNotifyLockRedisDAO; -import cn.iocoder.yudao.module.pay.enums.notify.PayNotifyStatusEnum; -import cn.iocoder.yudao.module.pay.enums.notify.PayNotifyTypeEnum; -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 com.google.common.annotations.VisibleForTesting; -import lombok.extern.slf4j.Slf4j; -import org.springframework.context.annotation.Lazy; -import org.springframework.scheduling.annotation.Async; -import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; -import org.springframework.transaction.support.TransactionSynchronization; -import org.springframework.transaction.support.TransactionSynchronizationManager; - -import jakarta.annotation.Resource; -import jakarta.validation.Valid; -import java.time.Duration; -import java.time.LocalDateTime; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.TimeUnit; - -import static cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.addTime; -import static cn.iocoder.yudao.module.pay.framework.job.config.PayJobConfiguration.NOTIFY_THREAD_POOL_TASK_EXECUTOR; - -/** - * 支付通知 Core Service 实现类 - * - * @author 芋道源码 - */ -@Service -@Valid -@Slf4j -public class PayNotifyServiceImpl implements PayNotifyService { - - /** - * 通知超时时间,单位:秒 - */ - public static final int NOTIFY_TIMEOUT = 120; - /** - * {@link #NOTIFY_TIMEOUT} 的毫秒 - */ - public static final long NOTIFY_TIMEOUT_MILLIS = 120 * DateUtils.SECOND_MILLIS; - - @Resource - @Lazy // 循环依赖,避免报错 - private PayOrderService orderService; - @Resource - @Lazy // 循环依赖,避免报错 - private PayRefundService refundService; - @Resource - @Lazy // 循环依赖,避免报错 - private PayTransferService transferService; - - @Resource - private PayNotifyTaskMapper notifyTaskMapper; - @Resource - private PayNotifyLogMapper notifyLogMapper; - - @Resource(name = NOTIFY_THREAD_POOL_TASK_EXECUTOR) - private ThreadPoolTaskExecutor threadPoolTaskExecutor; - - @Resource - private PayNotifyLockRedisDAO notifyLockCoreRedisDAO; - - @Override - @Transactional(rollbackFor = Exception.class) - public void createPayNotifyTask(Integer type, Long dataId) { - PayNotifyTaskDO task = new PayNotifyTaskDO().setType(type).setDataId(dataId); - task.setStatus(PayNotifyStatusEnum.WAITING.getStatus()).setNextNotifyTime(LocalDateTime.now()) - .setNotifyTimes(0).setMaxNotifyTimes(PayNotifyTaskDO.NOTIFY_FREQUENCY.length + 1); - // 补充 appId + notifyUrl + merchant* 字段 - if (Objects.equals(task.getType(), PayNotifyTypeEnum.ORDER.getType())) { - PayOrderDO order = orderService.getOrder(task.getDataId()); // 不进行非空判断,有问题直接异常 - task.setAppId(order.getAppId()).setNotifyUrl(order.getNotifyUrl()) - .setMerchantOrderId(order.getMerchantOrderId()); - } else if (Objects.equals(task.getType(), PayNotifyTypeEnum.REFUND.getType())) { - PayRefundDO refund = refundService.getRefund(task.getDataId()); - task.setAppId(refund.getAppId()).setNotifyUrl(refund.getNotifyUrl()) - .setMerchantOrderId(refund.getMerchantOrderId()).setMerchantRefundId(refund.getMerchantRefundId()); - } else if (Objects.equals(task.getType(), PayNotifyTypeEnum.TRANSFER.getType())) { - PayTransferDO transfer = transferService.getTransfer(task.getDataId()); - task.setAppId(transfer.getAppId()).setNotifyUrl(transfer.getNotifyUrl()) - .setMerchantTransferId(transfer.getMerchantTransferId()); - } - - // 执行插入 - notifyTaskMapper.insert(task); - - // 必须在事务提交后,在发起任务,否则 PayNotifyTaskDO 还没入库,就提前回调接入的业务 - TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() { - - @Override - public void afterCommit() { - // 异步的原因:避免阻塞当前事务,无需等待结果 - getSelf().executeNotifyAsync(task); - } - - }); - } - - @Override - public int executeNotify() throws InterruptedException { - // 获得需要通知的任务 - List tasks = notifyTaskMapper.selectListByNotify(); - if (CollUtil.isEmpty(tasks)) { - return 0; - } - - // 遍历,逐个通知 - CountDownLatch latch = new CountDownLatch(tasks.size()); - tasks.forEach(task -> threadPoolTaskExecutor.execute(() -> { - try { - executeNotify(task); - } finally { - latch.countDown(); - } - })); - // 等待完成 - awaitExecuteNotify(latch); - // 返回执行完成的任务数(成功 + 失败) - return tasks.size(); - } - - /** - * 等待全部支付通知的完成 - * 每 1 秒会打印一次剩余任务数量 - * - * @param latch Latch - * @throws InterruptedException 如果被打断 - */ - private void awaitExecuteNotify(CountDownLatch latch) throws InterruptedException { - long size = latch.getCount(); - for (int i = 0; i < NOTIFY_TIMEOUT; i++) { - if (latch.await(1L, TimeUnit.SECONDS)) { - return; - } - log.info("[awaitExecuteNotify][任务处理中, 总任务数({}) 剩余任务数({})]", size, latch.getCount()); - } - log.error("[awaitExecuteNotify][任务未处理完,总任务数({}) 剩余任务数({})]", size, latch.getCount()); - } - - /** - * 异步执行单个支付通知 - * - * @param task 通知任务 - */ - @Async - public void executeNotifyAsync(PayNotifyTaskDO task) { - executeNotify(task); - } - - /** - * 【加锁】执行单个支付通知 - * - * @param task 通知任务 - */ - public void executeNotify(PayNotifyTaskDO task) { - // 分布式锁,避免并发问题 - notifyLockCoreRedisDAO.lock(task.getId(), NOTIFY_TIMEOUT_MILLIS, () -> { - // 校验,当前任务是否已经被通知过 - // 虽然已经通过分布式加锁,但是可能同时满足通知的条件,然后都去获得锁。此时,第一个执行完后,第二个还是能拿到锁,然后会再执行一次。 - // 因此,此处我们通过第 notifyTimes 通知次数是否匹配来判断 - PayNotifyTaskDO dbTask = notifyTaskMapper.selectById(task.getId()); - if (ObjectUtil.notEqual(task.getNotifyTimes(), dbTask.getNotifyTimes())) { - log.warn("[executeNotifySync][task({}) 任务被忽略,原因是它的通知不是第 ({}) 次,可能是因为并发执行了]", - JsonUtils.toJsonString(task), dbTask.getNotifyTimes()); - return; - } - - // 执行通知 - getSelf().executeNotify0(dbTask); - }); - } - - @Transactional(rollbackFor = Exception.class) - public void executeNotify0(PayNotifyTaskDO task) { - // 发起回调 - CommonResult invokeResult = null; - Throwable invokeException = null; - try { - invokeResult = executeNotifyInvoke(task); - } catch (Throwable e) { - invokeException = e; - } - - // 处理结果 - Integer newStatus = processNotifyResult(task, invokeResult, invokeException); - - // 记录 PayNotifyLog 日志 - String response = invokeException != null ? ExceptionUtil.getRootCauseMessage(invokeException) : - JsonUtils.toJsonString(invokeResult); - notifyLogMapper.insert(PayNotifyLogDO.builder().taskId(task.getId()) - .notifyTimes(task.getNotifyTimes() + 1).status(newStatus).response(response).build()); - } - - /** - * 执行单个支付任务的 HTTP 调用 - * - * @param task 通知任务 - * @return HTTP 响应 - */ - private CommonResult executeNotifyInvoke(PayNotifyTaskDO task) { - // 拼接 body 参数 - Object request; - if (Objects.equals(task.getType(), PayNotifyTypeEnum.ORDER.getType())) { - request = PayOrderNotifyReqDTO.builder().merchantOrderId(task.getMerchantOrderId()) - .payOrderId(task.getDataId()).build(); - } else if (Objects.equals(task.getType(), PayNotifyTypeEnum.REFUND.getType())) { - request = PayRefundNotifyReqDTO.builder().merchantOrderId(task.getMerchantOrderId()) - .merchantRefundId(task.getMerchantRefundId()) - .payRefundId(task.getDataId()).build(); - } else if (Objects.equals(task.getType(), PayNotifyTypeEnum.TRANSFER.getType())) { - request = PayTransferNotifyReqDTO.builder().merchantTransferId(task.getMerchantTransferId()) - .payTransferId(task.getDataId()).build(); - } else { - throw new RuntimeException("未知的通知任务类型:" + JsonUtils.toJsonString(task)); - } - // 拼接 header 参数 - Map headers = new HashMap<>(); - TenantUtils.addTenantHeader(headers, task.getTenantId()); - - // 发起请求 - try (HttpResponse response = HttpUtil.createPost(task.getNotifyUrl()) - .body(JsonUtils.toJsonString(request)).addHeaders(headers) - .timeout((int) NOTIFY_TIMEOUT_MILLIS).execute()) { - // 解析结果 - return JsonUtils.parseObject(response.body(), CommonResult.class); - } - } - - /** - * 处理并更新通知结果 - * - * @param task 通知任务 - * @param invokeResult 通知结果 - * @param invokeException 通知异常 - * @return 最终任务的状态 - */ - @VisibleForTesting - Integer processNotifyResult(PayNotifyTaskDO task, CommonResult invokeResult, Throwable invokeException) { - // 设置通用的更新 PayNotifyTaskDO 的字段 - PayNotifyTaskDO updateTask = new PayNotifyTaskDO() - .setId(task.getId()) - .setLastExecuteTime(LocalDateTime.now()) - .setNotifyTimes(task.getNotifyTimes() + 1); - - // 情况一:调用成功 - if (invokeResult != null && invokeResult.isSuccess()) { - updateTask.setStatus(PayNotifyStatusEnum.SUCCESS.getStatus()); - notifyTaskMapper.updateById(updateTask); - return updateTask.getStatus(); - } - - // 情况二:调用失败、调用异常 - // 2.1 超过最大回调次数 - if (updateTask.getNotifyTimes() >= PayNotifyTaskDO.NOTIFY_FREQUENCY.length) { - updateTask.setStatus(PayNotifyStatusEnum.FAILURE.getStatus()); - notifyTaskMapper.updateById(updateTask); - return updateTask.getStatus(); - } - // 2.2 未超过最大回调次数 - updateTask.setNextNotifyTime(addTime(Duration.ofSeconds(PayNotifyTaskDO.NOTIFY_FREQUENCY[updateTask.getNotifyTimes()]))); - updateTask.setStatus(invokeException != null ? PayNotifyStatusEnum.REQUEST_FAILURE.getStatus() - : PayNotifyStatusEnum.REQUEST_SUCCESS.getStatus()); - notifyTaskMapper.updateById(updateTask); - return updateTask.getStatus(); - } - - @Override - public PayNotifyTaskDO getNotifyTask(Long id) { - return notifyTaskMapper.selectById(id); - } - - @Override - public PageResult getNotifyTaskPage(PayNotifyTaskPageReqVO pageReqVO) { - return notifyTaskMapper.selectPage(pageReqVO); - } - - @Override - public List getNotifyLogList(Long taskId) { - return notifyLogMapper.selectListByTaskId(taskId); - } - - /** - * 获得自身的代理对象,解决 AOP 生效问题 - * - * @return 自己 - */ - private PayNotifyServiceImpl getSelf() { - return SpringUtil.getBean(getClass()); - } - -} 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 deleted file mode 100755 index c4496f8..0000000 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/order/PayOrderService.java +++ /dev/null @@ -1,159 +0,0 @@ -package cn.iocoder.yudao.module.pay.service.order; - -import cn.iocoder.yudao.framework.common.pojo.PageResult; -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; -import cn.iocoder.yudao.module.pay.controller.admin.order.vo.PayOrderSubmitReqVO; -import cn.iocoder.yudao.module.pay.controller.admin.order.vo.PayOrderSubmitRespVO; -import cn.iocoder.yudao.module.pay.dal.dataobject.order.PayOrderDO; -import cn.iocoder.yudao.module.pay.dal.dataobject.order.PayOrderExtensionDO; - -import jakarta.validation.Valid; -import jakarta.validation.constraints.NotEmpty; -import java.time.LocalDateTime; -import java.util.Collection; -import java.util.List; - -/** - * 支付订单 Service 接口 - * - * @author aquan - */ -public interface PayOrderService { - - /** - * 获得支付订单 - * - * @param id 编号 - * @return 支付订单 - */ - PayOrderDO getOrder(Long id); - - /** - * 获得支付订单 - * - * @param appId 应用编号 - * @param merchantOrderId 商户订单编号 - * @return 支付订单 - */ - PayOrderDO getOrder(Long appId, String merchantOrderId); - - /** - * 获得支付订单列表 - * - * @param ids 编号数组 - * @return 支付订单列表 - */ - List getOrderList(Collection ids); - - /** - * 获得指定应用的订单数量 - * - * @param appId 应用编号 - * @return 订单数量 - */ - Long getOrderCountByAppId(Long appId); - - /** - * 获得支付订单分页 - * - * @param pageReqVO 分页查询 - * @return 支付订单分页 - */ - PageResult getOrderPage(PayOrderPageReqVO pageReqVO); - - /** - * 获得支付订单列表, 用于 Excel 导出 - * - * @param exportReqVO 查询条件 - * @return 支付订单列表 - */ - List getOrderList(PayOrderExportReqVO exportReqVO); - - /** - * 创建支付单 - * - * @param reqDTO 创建请求 - * @return 支付单编号 - */ - Long createOrder(@Valid PayOrderCreateReqDTO reqDTO); - - /** - * 提交支付 - * 此时,会发起支付渠道的调用 - * - * @param reqVO 提交请求 - * @param userIp 提交 IP - * @return 提交结果 - */ - PayOrderSubmitRespVO submitOrder(@Valid PayOrderSubmitReqVO reqVO, - @NotEmpty(message = "提交 IP 不能为空") String userIp); - - /** - * 通知支付单成功 - * - * @param channelId 渠道编号 - * @param notify 通知 - */ - void notifyOrder(Long channelId, PayOrderRespDTO notify); - - /** - * 更新支付订单的退款金额 - * - * @param id 编号 - * @param incrRefundPrice 增加的退款金额 - */ - void updateOrderRefundPrice(Long id, Integer incrRefundPrice); - - /** - * 更新支付订单价格 - * - * @param id 支付单编号 - * @param payPrice 支付单价格 - */ - void updatePayOrderPrice(Long id, Integer payPrice); - - /** - * 获得支付订单 - * - * @param id 编号 - * @return 支付订单 - */ - PayOrderExtensionDO getOrderExtension(Long id); - - /** - * 获得支付订单 - * - * @param no 支付订单 no - * @return 支付订单 - */ - PayOrderExtensionDO getOrderExtensionByNo(String no); - - /** - * 同步订单的支付状态 - * - * @param minCreateTime 最小创建时间 - * @return 同步到已支付的订单数量 - */ - int syncOrder(LocalDateTime minCreateTime); - - /** - * 同步订单的支付状态 - * - * 1. Quietly 表示,即使同步失败,也不会抛出异常 - * 2. 什么时候回出现异常?因为是主动同步,可能和支付渠道的异步回调存在并发冲突,导致抛出异常 - * - * @param id 订单编号 - */ - void syncOrderQuietly(Long id); - - /** - * 将已过期的订单,状态修改为已关闭 - * - * @return 过期的订单数量 - */ - int expireOrder(); - -} 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 deleted file mode 100755 index f468355..0000000 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/order/PayOrderServiceImpl.java +++ /dev/null @@ -1,605 +0,0 @@ -package cn.iocoder.yudao.module.pay.service.order; - -import cn.hutool.core.collection.CollUtil; -import cn.hutool.core.util.ObjectUtil; -import cn.hutool.core.util.StrUtil; -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.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; -import cn.iocoder.yudao.module.pay.controller.admin.order.vo.PayOrderPageReqVO; -import cn.iocoder.yudao.module.pay.controller.admin.order.vo.PayOrderSubmitReqVO; -import cn.iocoder.yudao.module.pay.controller.admin.order.vo.PayOrderSubmitRespVO; -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.channel.PayChannelDO; -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.mysql.order.PayOrderExtensionMapper; -import cn.iocoder.yudao.module.pay.dal.mysql.order.PayOrderMapper; -import cn.iocoder.yudao.module.pay.dal.redis.no.PayNoRedisDAO; -import cn.iocoder.yudao.module.pay.enums.notify.PayNotifyTypeEnum; -import cn.iocoder.yudao.module.pay.enums.order.PayOrderStatusEnum; -import cn.iocoder.yudao.module.pay.framework.pay.config.PayProperties; -import cn.iocoder.yudao.module.pay.service.app.PayAppService; -import cn.iocoder.yudao.module.pay.service.channel.PayChannelService; -import cn.iocoder.yudao.module.pay.service.notify.PayNotifyService; -import com.google.common.annotations.VisibleForTesting; -import lombok.extern.slf4j.Slf4j; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; -import org.springframework.validation.annotation.Validated; - -import jakarta.annotation.Resource; -import java.time.LocalDateTime; -import java.util.Collection; -import java.util.Collections; -import java.util.List; -import java.util.Objects; - -import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; -import static cn.iocoder.yudao.framework.common.util.json.JsonUtils.toJsonString; -import static cn.iocoder.yudao.module.pay.enums.ErrorCodeConstants.*; - -/** - * 支付订单 Service 实现类 - * - * @author aquan - */ -@Service -@Validated -@Slf4j -public class PayOrderServiceImpl implements PayOrderService { - - @Resource - private PayProperties payProperties; - - @Resource - private PayOrderMapper orderMapper; - @Resource - private PayOrderExtensionMapper orderExtensionMapper; - @Resource - private PayNoRedisDAO noRedisDAO; - - @Resource - private PayAppService appService; - @Resource - private PayChannelService channelService; - @Resource - private PayNotifyService notifyService; - - @Override - public PayOrderDO getOrder(Long id) { - return orderMapper.selectById(id); - } - - @Override - public PayOrderDO getOrder(Long appId, String merchantOrderId) { - return orderMapper.selectByAppIdAndMerchantOrderId(appId, merchantOrderId); - } - - @Override - public List getOrderList(Collection ids) { - if (CollUtil.isEmpty(ids)) { - return Collections.emptyList(); - } - return orderMapper.selectBatchIds(ids); - } - - @Override - public Long getOrderCountByAppId(Long appId) { - return orderMapper.selectCountByAppId(appId); - } - - @Override - public PageResult getOrderPage(PayOrderPageReqVO pageReqVO) { - return orderMapper.selectPage(pageReqVO); - } - - @Override - public List getOrderList(PayOrderExportReqVO exportReqVO) { - return orderMapper.selectList(exportReqVO); - } - - @Override - public Long createOrder(PayOrderCreateReqDTO reqDTO) { - // 校验 App - PayAppDO app = appService.validPayApp(reqDTO.getAppKey()); - - // 查询对应的支付交易单是否已经存在。如果是,则直接返回 - PayOrderDO order = orderMapper.selectByAppIdAndMerchantOrderId( - app.getId(), reqDTO.getMerchantOrderId()); - if (order != null) { - log.warn("[createOrder][appId({}) merchantOrderId({}) 已经存在对应的支付单({})]", order.getAppId(), - order.getMerchantOrderId(), toJsonString(order)); // 理论来说,不会出现这个情况 - return order.getId(); - } - - // 创建支付交易单 - order = PayOrderConvert.INSTANCE.convert(reqDTO).setAppId(app.getId()) - // 商户相关字段 - .setNotifyUrl(app.getOrderNotifyUrl()) - // 订单相关字段 - .setStatus(PayOrderStatusEnum.WAITING.getStatus()) - // 退款相关字段 - .setRefundPrice(0); - orderMapper.insert(order); - return order.getId(); - } - - @Override // 注意,这里不能添加事务注解,避免调用支付渠道失败时,将 PayOrderExtensionDO 回滚了 - public PayOrderSubmitRespVO submitOrder(PayOrderSubmitReqVO reqVO, String userIp) { - // 1.1 获得 PayOrderDO ,并校验其是否存在 - PayOrderDO order = validateOrderCanSubmit(reqVO.getId()); - // 1.32 校验支付渠道是否有效 - PayChannelDO channel = validateChannelCanSubmit(order.getAppId(), reqVO.getChannelCode()); - PayClient client = channelService.getPayClient(channel.getId()); - - // 2. 插入 PayOrderExtensionDO - String no = noRedisDAO.generate(payProperties.getOrderNoPrefix()); - PayOrderExtensionDO orderExtension = PayOrderConvert.INSTANCE.convert(reqVO, userIp) - .setOrderId(order.getId()).setNo(no) - .setChannelId(channel.getId()).setChannelCode(channel.getCode()) - .setStatus(PayOrderStatusEnum.WAITING.getStatus()); - orderExtensionMapper.insert(orderExtension); - - // 3. 调用三方接口 - PayOrderUnifiedReqDTO unifiedOrderReqDTO = PayOrderConvert.INSTANCE.convert2(reqVO, userIp) - // 商户相关的字段 - .setOutTradeNo(orderExtension.getNo()) // 注意,此处使用的是 PayOrderExtensionDO.no 属性! - .setSubject(order.getSubject()).setBody(order.getBody()) - .setNotifyUrl(genChannelOrderNotifyUrl(channel)) - .setReturnUrl(reqVO.getReturnUrl()) - // 订单相关字段 - .setPrice(order.getPrice()).setExpireTime(order.getExpireTime()); - PayOrderRespDTO unifiedOrderResp = client.unifiedOrder(unifiedOrderReqDTO); - - // 4. 如果调用直接支付成功,则直接更新支付单状态为成功。例如说:付款码支付,免密支付时,就直接验证支付成功 - if (unifiedOrderResp != null) { - try { - getSelf().notifyOrder(channel, unifiedOrderResp); - } catch (Exception e) { - // 兼容 https://gitee.com/zhijiantianya/yudao-cloud/issues/I8SM9H 场景 - // 支付宝或微信扫码之后时,由于 PayClient 是直接返回支付成功,而支付也会有回调,导致存在并发更新问题,此时一般是可以 try catch 直接忽略 - log.warn("[submitOrder][order({}) channel({}) 支付结果({}) 通知时发生异常,可能是并发问题]", - order, channel, unifiedOrderResp, e); - } - // 如有渠道错误码,则抛出业务异常,提示用户 - if (StrUtil.isNotEmpty(unifiedOrderResp.getChannelErrorCode())) { - throw exception(PAY_ORDER_SUBMIT_CHANNEL_ERROR, unifiedOrderResp.getChannelErrorCode(), - unifiedOrderResp.getChannelErrorMsg()); - } - // 此处需要读取最新的状态 - order = orderMapper.selectById(order.getId()); - } - return PayOrderConvert.INSTANCE.convert(order, unifiedOrderResp); - } - - private PayOrderDO validateOrderCanSubmit(Long id) { - PayOrderDO order = orderMapper.selectById(id); - if (order == null) { // 是否存在 - throw exception(PAY_ORDER_NOT_FOUND); - } - if (PayOrderStatusEnum.isSuccess(order.getStatus())) { // 校验状态,发现已支付 - throw exception(PAY_ORDER_STATUS_IS_SUCCESS); - } - if (!PayOrderStatusEnum.WAITING.getStatus().equals(order.getStatus())) { // 校验状态,必须是待支付 - throw exception(PAY_ORDER_STATUS_IS_NOT_WAITING); - } - if (LocalDateTimeUtils.beforeNow(order.getExpireTime())) { // 校验是否过期 - throw exception(PAY_ORDER_IS_EXPIRED); - } - - // 【重要】校验是否支付拓展单已支付,只是没有回调、或者数据不正常 - validateOrderActuallyPaid(id); - return order; - } - - /** - * 校验支付订单实际已支付 - * - * @param id 支付编号 - */ - @VisibleForTesting - void validateOrderActuallyPaid(Long id) { - List orderExtensions = orderExtensionMapper.selectListByOrderId(id); - orderExtensions.forEach(orderExtension -> { - // 情况一:校验数据库中的 orderExtension 是不是已支付 - if (PayOrderStatusEnum.isSuccess(orderExtension.getStatus())) { - log.warn("[validateOrderCanSubmit][order({}) 的 extension({}) 已支付,可能是数据不一致]", - id, orderExtension.getId()); - throw exception(PAY_ORDER_EXTENSION_IS_PAID); - } - // 情况二:调用三方接口,查询支付单状态,是不是已支付 - PayClient payClient = channelService.getPayClient(orderExtension.getChannelId()); - if (payClient == null) { - log.error("[validateOrderCanSubmit][渠道编号({}) 找不到对应的支付客户端]", orderExtension.getChannelId()); - return; - } - PayOrderRespDTO respDTO = payClient.getOrder(orderExtension.getNo()); - if (respDTO != null && PayOrderStatusEnum.isSuccess(respDTO.getStatus())) { - log.warn("[validateOrderCanSubmit][order({}) 的 PayOrderRespDTO({}) 已支付,可能是回调延迟]", - id, toJsonString(respDTO)); - throw exception(PAY_ORDER_EXTENSION_IS_PAID); - } - }); - } - - private PayChannelDO validateChannelCanSubmit(Long appId, String channelCode) { - // 校验 App - appService.validPayApp(appId); - // 校验支付渠道是否有效 - PayChannelDO channel = channelService.validPayChannel(appId, channelCode); - PayClient client = channelService.getPayClient(channel.getId()); - if (client == null) { - log.error("[validatePayChannelCanSubmit][渠道编号({}) 找不到对应的支付客户端]", channel.getId()); - throw exception(CHANNEL_NOT_FOUND); - } - return channel; - } - - /** - * 根据支付渠道的编码,生成支付渠道的回调地址 - * - * @param channel 支付渠道 - * @return 支付渠道的回调地址 配置地址 + "/" + channel id - */ - private String genChannelOrderNotifyUrl(PayChannelDO channel) { - return payProperties.getOrderNotifyUrl() + "/" + channel.getId(); - } - - @Override - public void notifyOrder(Long channelId, PayOrderRespDTO notify) { - // 校验支付渠道是否有效 - PayChannelDO channel = channelService.validPayChannel(channelId); - // 更新支付订单为已支付 - TenantUtils.execute(channel.getTenantId(), () -> getSelf().notifyOrder(channel, notify)); - } - - /** - * 通知并更新订单的支付结果 - * - * @param channel 支付渠道 - * @param notify 通知 - */ - @Transactional(rollbackFor = Exception.class) - // 注意,如果是方法内调用该方法,需要通过 getSelf().notifyPayOrder(channel, notify) 调用,否则事务不生效 - public void notifyOrder(PayChannelDO channel, PayOrderRespDTO notify) { - // 情况一:支付成功的回调 - if (PayOrderStatusEnum.isSuccess(notify.getStatus())) { - notifyOrderSuccess(channel, notify); - return; - } - // 情况二:支付失败的回调 - if (PayOrderStatusEnum.isClosed(notify.getStatus())) { - notifyOrderClosed(channel, notify); - } - // 情况三:WAITING:无需处理 - // 情况四:REFUND:通过退款回调处理 - } - - private void notifyOrderSuccess(PayChannelDO channel, PayOrderRespDTO notify) { - // 1. 更新 PayOrderExtensionDO 支付成功 - PayOrderExtensionDO orderExtension = updateOrderSuccess(notify); - // 2. 更新 PayOrderDO 支付成功 - Boolean paid = updateOrderSuccess(channel, orderExtension, notify); - if (paid) { // 如果之前已经成功回调,则直接返回,不用重复记录支付通知记录;例如说:支付平台重复回调 - return; - } - - // 3. 插入支付通知记录 - notifyService.createPayNotifyTask(PayNotifyTypeEnum.ORDER.getType(), - orderExtension.getOrderId()); - } - - /** - * 更新 PayOrderExtensionDO 支付成功 - * - * @param notify 通知 - * @return PayOrderExtensionDO 对象 - */ - private PayOrderExtensionDO updateOrderSuccess(PayOrderRespDTO notify) { - // 1. 查询 PayOrderExtensionDO - PayOrderExtensionDO orderExtension = orderExtensionMapper.selectByNo(notify.getOutTradeNo()); - if (orderExtension == null) { - throw exception(PAY_ORDER_EXTENSION_NOT_FOUND); - } - if (PayOrderStatusEnum.isSuccess(orderExtension.getStatus())) { // 如果已经是成功,直接返回,不用重复更新 - log.info("[updateOrderExtensionSuccess][orderExtension({}) 已经是已支付,无需更新]", orderExtension.getId()); - return orderExtension; - } - if (ObjectUtil.notEqual(orderExtension.getStatus(), PayOrderStatusEnum.WAITING.getStatus())) { // 校验状态,必须是待支付 - throw exception(PAY_ORDER_EXTENSION_STATUS_IS_NOT_WAITING); - } - - // 2. 更新 PayOrderExtensionDO - int updateCounts = orderExtensionMapper.updateByIdAndStatus(orderExtension.getId(), orderExtension.getStatus(), - PayOrderExtensionDO.builder().status(PayOrderStatusEnum.SUCCESS.getStatus()).channelNotifyData(toJsonString(notify)).build()); - if (updateCounts == 0) { // 校验状态,必须是待支付 - throw exception(PAY_ORDER_EXTENSION_STATUS_IS_NOT_WAITING); - } - log.info("[updateOrderExtensionSuccess][orderExtension({}) 更新为已支付]", orderExtension.getId()); - return orderExtension; - } - - /** - * 更新 PayOrderDO 支付成功 - * - * @param channel 支付渠道 - * @param orderExtension 支付拓展单 - * @param notify 通知回调 - * @return 是否之前已经成功回调 - */ - private Boolean updateOrderSuccess(PayChannelDO channel, PayOrderExtensionDO orderExtension, - PayOrderRespDTO notify) { - // 1. 判断 PayOrderDO 是否处于待支付 - PayOrderDO order = orderMapper.selectById(orderExtension.getOrderId()); - if (order == null) { - throw exception(PAY_ORDER_NOT_FOUND); - } - if (PayOrderStatusEnum.isSuccess(order.getStatus()) // 如果已经是成功,直接返回,不用重复更新 - && Objects.equals(order.getExtensionId(), orderExtension.getId())) { - log.info("[updateOrderExtensionSuccess][order({}) 已经是已支付,无需更新]", order.getId()); - return true; - } - if (!PayOrderStatusEnum.WAITING.getStatus().equals(order.getStatus())) { // 校验状态,必须是待支付 - throw exception(PAY_ORDER_STATUS_IS_NOT_WAITING); - } - - // 2. 更新 PayOrderDO - int updateCounts = orderMapper.updateByIdAndStatus(order.getId(), PayOrderStatusEnum.WAITING.getStatus(), - PayOrderDO.builder().status(PayOrderStatusEnum.SUCCESS.getStatus()) - .channelId(channel.getId()).channelCode(channel.getCode()) - .successTime(notify.getSuccessTime()).extensionId(orderExtension.getId()).no(orderExtension.getNo()) - .channelOrderNo(notify.getChannelOrderNo()).channelUserId(notify.getChannelUserId()) - .channelFeeRate(channel.getFeeRate()) - .channelFeePrice(MoneyUtils.calculateRatePrice(order.getPrice(), channel.getFeeRate())) - .build()); - if (updateCounts == 0) { // 校验状态,必须是待支付 - throw exception(PAY_ORDER_STATUS_IS_NOT_WAITING); - } - log.info("[updateOrderExtensionSuccess][order({}) 更新为已支付]", order.getId()); - return false; - } - - private void notifyOrderClosed(PayChannelDO channel, PayOrderRespDTO notify) { - updateOrderExtensionClosed(channel, notify); - } - - @SuppressWarnings("unused") - private void updateOrderExtensionClosed(PayChannelDO channel, PayOrderRespDTO notify) { - // 1. 查询 PayOrderExtensionDO - PayOrderExtensionDO orderExtension = orderExtensionMapper.selectByNo(notify.getOutTradeNo()); - if (orderExtension == null) { - throw exception(PAY_ORDER_EXTENSION_NOT_FOUND); - } - if (PayOrderStatusEnum.isClosed(orderExtension.getStatus())) { // 如果已经是关闭,直接返回,不用重复更新 - log.info("[updateOrderExtensionClosed][orderExtension({}) 已经是支付关闭,无需更新]", orderExtension.getId()); - return; - } - // 一般出现先是支付成功,然后支付关闭,都是全部退款导致关闭的场景。这个情况,我们不更新支付拓展单,只通过退款流程,更新支付单 - if (PayOrderStatusEnum.isSuccess(orderExtension.getStatus())) { - log.info("[updateOrderExtensionClosed][orderExtension({}) 是已支付,无需更新为支付关闭]", orderExtension.getId()); - return; - } - if (ObjectUtil.notEqual(orderExtension.getStatus(), PayOrderStatusEnum.WAITING.getStatus())) { // 校验状态,必须是待支付 - throw exception(PAY_ORDER_EXTENSION_STATUS_IS_NOT_WAITING); - } - - // 2. 更新 PayOrderExtensionDO - int updateCounts = orderExtensionMapper.updateByIdAndStatus(orderExtension.getId(), orderExtension.getStatus(), - PayOrderExtensionDO.builder().status(PayOrderStatusEnum.CLOSED.getStatus()).channelNotifyData(toJsonString(notify)) - .channelErrorCode(notify.getChannelErrorCode()).channelErrorMsg(notify.getChannelErrorMsg()).build()); - if (updateCounts == 0) { // 校验状态,必须是待支付 - throw exception(PAY_ORDER_EXTENSION_STATUS_IS_NOT_WAITING); - } - log.info("[updateOrderExtensionClosed][orderExtension({}) 更新为支付关闭]", orderExtension.getId()); - } - - @Override - public void updateOrderRefundPrice(Long id, Integer incrRefundPrice) { - PayOrderDO order = orderMapper.selectById(id); - if (order == null) { - throw exception(PAY_ORDER_NOT_FOUND); - } - if (!PayOrderStatusEnum.isSuccessOrRefund(order.getStatus())) { - throw exception(PAY_ORDER_REFUND_FAIL_STATUS_ERROR); - } - if (order.getRefundPrice() + incrRefundPrice > order.getPrice()) { - throw exception(REFUND_PRICE_EXCEED); - } - - // 更新订单 - PayOrderDO updateObj = new PayOrderDO() - .setRefundPrice(order.getRefundPrice() + incrRefundPrice) - .setStatus(PayOrderStatusEnum.REFUND.getStatus()); - int updateCount = orderMapper.updateByIdAndStatus(id, order.getStatus(), updateObj); - if (updateCount == 0) { - throw exception(PAY_ORDER_REFUND_FAIL_STATUS_ERROR); - } - } - - @Override - public void updatePayOrderPrice(Long id, Integer payPrice) { - PayOrderDO order = orderMapper.selectById(id); - if (order == null) { - throw exception(PAY_ORDER_NOT_FOUND); - } - if (ObjectUtil.notEqual(PayOrderStatusEnum.WAITING.getStatus(), order.getStatus())) { - throw exception(PAY_ORDER_STATUS_IS_NOT_WAITING); - } - if (ObjectUtil.equal(order.getPrice(), payPrice)) { - return; - } - - orderMapper.updateById(new PayOrderDO().setId(order.getId()).setPrice(payPrice)); - } - - @Override - public PayOrderExtensionDO getOrderExtension(Long id) { - return orderExtensionMapper.selectById(id); - } - - @Override - public PayOrderExtensionDO getOrderExtensionByNo(String no) { - return orderExtensionMapper.selectByNo(no); - } - - @Override - public int syncOrder(LocalDateTime minCreateTime) { - // 1. 查询指定创建时间前的待支付订单 - List orderExtensions = orderExtensionMapper.selectListByStatusAndCreateTimeGe( - PayOrderStatusEnum.WAITING.getStatus(), minCreateTime); - if (CollUtil.isEmpty(orderExtensions)) { - return 0; - } - // 2. 遍历执行 - int count = 0; - for (PayOrderExtensionDO orderExtension : orderExtensions) { - count += syncOrder(orderExtension) ? 1 : 0; - } - return count; - } - - @Override - public void syncOrderQuietly(Long id) { - // 1. 查询待支付订单 - List orderExtensions = orderExtensionMapper.selectListByOrderIdAndStatus(id, - PayOrderStatusEnum.WAITING.getStatus()); - - // 2. 遍历执行 - for (PayOrderExtensionDO orderExtension : orderExtensions) { - syncOrder(orderExtension); - } - } - - /** - * 同步单个支付拓展单 - * - * @param orderExtension 支付拓展单 - * @return 是否已支付 - */ - private boolean syncOrder(PayOrderExtensionDO orderExtension) { - try { - // 1.1 查询支付订单信息 - PayClient payClient = channelService.getPayClient(orderExtension.getChannelId()); - if (payClient == null) { - log.error("[syncOrder][渠道编号({}) 找不到对应的支付客户端]", orderExtension.getChannelId()); - return false; - } - PayOrderRespDTO respDTO = payClient.getOrder(orderExtension.getNo()); - // 如果查询到订单不存在,PayClient 返回的状态为关闭。但此时不能关闭订单。存在以下一种场景: - // 拉起渠道支付后,短时间内用户未及时完成支付,但是该订单同步定时任务恰巧自动触发了,主动查询结果为订单不存在。 - // 当用户支付成功之后,该订单状态在渠道的回调中无法从已关闭改为已支付,造成重大影响。 - // 考虑此定时任务是异常场景的兜底操作,因此这里不做变更,优先以回调为准。 - // 让订单自动随着支付渠道那边一起等到过期,确保渠道先过期关闭支付入口,而后通过订单过期定时任务关闭自己的订单。 - if (PayOrderStatusEnum.isClosed(respDTO.getStatus())) { - return false; - } - // 1.2 回调支付结果 - notifyOrder(orderExtension.getChannelId(), respDTO); - - // 2. 如果是已支付,则返回 true - return PayOrderStatusEnum.isSuccess(respDTO.getStatus()); - } catch (Throwable e) { - log.error("[syncOrder][orderExtension({}) 同步支付状态异常]", orderExtension.getId(), e); - return false; - } - } - - @Override - public int expireOrder() { - // 1. 查询过期的待支付订单 - List orders = orderMapper.selectListByStatusAndExpireTimeLt( - PayOrderStatusEnum.WAITING.getStatus(), LocalDateTime.now()); - if (CollUtil.isEmpty(orders)) { - return 0; - } - - // 2. 遍历执行 - int count = 0; - for (PayOrderDO order : orders) { - count += expireOrder(order) ? 1 : 0; - } - return count; - } - - /** - * 同步单个支付单 - * - * @param order 支付单 - * @return 是否已过期 - */ - private boolean expireOrder(PayOrderDO order) { - try { - // 1. 需要先处理关联的支付拓展单,避免错误的过期已支付 or 已退款的订单 - List orderExtensions = orderExtensionMapper.selectListByOrderId(order.getId()); - for (PayOrderExtensionDO orderExtension : orderExtensions) { - if (PayOrderStatusEnum.isClosed(orderExtension.getStatus())) { - continue; - } - // 情况一:校验数据库中的 orderExtension 是不是已支付 - if (PayOrderStatusEnum.isSuccess(orderExtension.getStatus())) { - log.error("[expireOrder][order({}) 的 extension({}) 已支付,可能是数据不一致]", - order.getId(), orderExtension.getId()); - return false; - } - // 情况二:调用三方接口,查询支付单状态,是不是已支付/已退款 - PayClient payClient = channelService.getPayClient(orderExtension.getChannelId()); - if (payClient == null) { - log.error("[expireOrder][渠道编号({}) 找不到对应的支付客户端]", orderExtension.getChannelId()); - return false; - } - PayOrderRespDTO respDTO = payClient.getOrder(orderExtension.getNo()); - 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 (PayOrderStatusEnum.isSuccess(respDTO.getStatus())) { - notifyOrder(orderExtension.getChannelId(), respDTO); - return false; - } - // 兜底逻辑:将支付拓展单更新为已关闭 - PayOrderExtensionDO updateObj = new PayOrderExtensionDO().setStatus(PayOrderStatusEnum.CLOSED.getStatus()) - .setChannelNotifyData(toJsonString(respDTO)); - if (orderExtensionMapper.updateByIdAndStatus(orderExtension.getId(), PayOrderStatusEnum.WAITING.getStatus(), - updateObj) == 0) { - log.error("[expireOrder][extension({}) 更新为支付关闭失败]", orderExtension.getId()); - return false; - } - log.info("[expireOrder][extension({}) 更新为支付关闭成功]", orderExtension.getId()); - } - - // 2. 都没有上述情况,可以安心更新为已关闭 - PayOrderDO updateObj = new PayOrderDO().setStatus(PayOrderStatusEnum.CLOSED.getStatus()); - if (orderMapper.updateByIdAndStatus(order.getId(), order.getStatus(), updateObj) == 0) { - log.error("[expireOrder][order({}) 更新为支付关闭失败]", order.getId()); - return false; - } - log.info("[expireOrder][order({}) 更新为支付关闭失败]", order.getId()); - return true; - } catch (Throwable e) { - log.error("[expireOrder][order({}) 过期订单异常]", order.getId(), e); - return false; - } - } - - /** - * 获得自身的代理对象,解决 AOP 生效问题 - * - * @return 自己 - */ - private PayOrderServiceImpl getSelf() { - return SpringUtil.getBean(getClass()); - } - -} 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 deleted file mode 100755 index 38b6e15..0000000 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/refund/PayRefundService.java +++ /dev/null @@ -1,82 +0,0 @@ -package cn.iocoder.yudao.module.pay.service.refund; - -import cn.iocoder.yudao.framework.common.pojo.PageResult; -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; -import cn.iocoder.yudao.module.pay.dal.dataobject.refund.PayRefundDO; - -import java.util.List; - -/** - * 退款订单 Service 接口 - * - * @author aquan - */ -public interface PayRefundService { - - /** - * 获得退款订单 - * - * @param id 编号 - * @return 退款订单 - */ - PayRefundDO getRefund(Long id); - - /** - * 获得退款订单 - * - * @param no 外部退款单号 - * @return 退款订单 - */ - PayRefundDO getRefundByNo(String no); - - /** - * 获得指定应用的退款数量 - * - * @param appId 应用编号 - * @return 退款数量 - */ - Long getRefundCountByAppId(Long appId); - - /** - * 获得退款订单分页 - * - * @param pageReqVO 分页查询 - * @return 退款订单分页 - */ - PageResult getRefundPage(PayRefundPageReqVO pageReqVO); - - /** - * 获得退款订单列表, 用于 Excel 导出 - * - * @param exportReqVO 查询条件 - * @return 退款订单列表 - */ - List getRefundList(PayRefundExportReqVO exportReqVO); - - /** - * 创建退款申请 - * - * @param reqDTO 退款申请信息 - * @return 退款单号 - */ - Long createRefund(PayRefundCreateReqDTO reqDTO); - - /** - * 渠道的退款通知 - * - * @param channelId 渠道编号 - * @param notify 通知 - */ - void notifyRefund(Long channelId, PayRefundRespDTO notify); - - /** - * 同步渠道退款的退款状态 - * - * @return 同步到状态的退款数量,包括退款成功、退款失败 - */ - int syncRefund(); - -} 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 deleted file mode 100755 index 051d46c..0000000 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/refund/PayRefundServiceImpl.java +++ /dev/null @@ -1,331 +0,0 @@ -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.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; -import cn.iocoder.yudao.module.pay.controller.admin.refund.vo.PayRefundPageReqVO; -import cn.iocoder.yudao.module.pay.convert.refund.PayRefundConvert; -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; -import cn.iocoder.yudao.module.pay.dal.dataobject.refund.PayRefundDO; -import cn.iocoder.yudao.module.pay.dal.mysql.refund.PayRefundMapper; -import cn.iocoder.yudao.module.pay.dal.redis.no.PayNoRedisDAO; -import cn.iocoder.yudao.module.pay.enums.notify.PayNotifyTypeEnum; -import cn.iocoder.yudao.module.pay.enums.order.PayOrderStatusEnum; -import cn.iocoder.yudao.module.pay.enums.refund.PayRefundStatusEnum; -import cn.iocoder.yudao.module.pay.framework.pay.config.PayProperties; -import cn.iocoder.yudao.module.pay.service.app.PayAppService; -import cn.iocoder.yudao.module.pay.service.channel.PayChannelService; -import cn.iocoder.yudao.module.pay.service.notify.PayNotifyService; -import cn.iocoder.yudao.module.pay.service.order.PayOrderService; -import jakarta.annotation.Resource; -import lombok.extern.slf4j.Slf4j; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; -import org.springframework.validation.annotation.Validated; - -import java.util.List; - -import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; -import static cn.iocoder.yudao.framework.common.util.json.JsonUtils.toJsonString; -import static cn.iocoder.yudao.module.pay.enums.ErrorCodeConstants.*; - -/** - * 退款订单 Service 实现类 - * - * @author jason - */ -@Service -@Slf4j -@Validated -public class PayRefundServiceImpl implements PayRefundService { - - @Resource - private PayProperties payProperties; - - @Resource - private PayRefundMapper refundMapper; - @Resource - private PayNoRedisDAO noRedisDAO; - - @Resource - private PayOrderService orderService; - @Resource - private PayAppService appService; - @Resource - private PayChannelService channelService; - @Resource - private PayNotifyService notifyService; - - @Override - public PayRefundDO getRefund(Long id) { - return refundMapper.selectById(id); - } - - @Override - public PayRefundDO getRefundByNo(String no) { - return refundMapper.selectByNo(no); - } - - @Override - public Long getRefundCountByAppId(Long appId) { - return refundMapper.selectCountByAppId(appId); - } - - @Override - public PageResult getRefundPage(PayRefundPageReqVO pageReqVO) { - return refundMapper.selectPage(pageReqVO); - } - - @Override - public List getRefundList(PayRefundExportReqVO exportReqVO) { - return refundMapper.selectList(exportReqVO); - } - - @Override - public Long createRefund(PayRefundCreateReqDTO reqDTO) { - // 1.1 校验 App - PayAppDO app = appService.validPayApp(reqDTO.getAppKey()); - // 1.2 校验支付订单 - PayOrderDO order = validatePayOrderCanRefund(reqDTO, app.getId()); - // 1.3 校验支付渠道是否有效 - PayChannelDO channel = channelService.validPayChannel(order.getChannelId()); - PayClient client = channelService.getPayClient(channel.getId()); - if (client == null) { - log.error("[refund][渠道编号({}) 找不到对应的支付客户端]", channel.getId()); - throw exception(CHANNEL_NOT_FOUND); - } - // 1.4 校验退款订单是否已经存在 - PayRefundDO refund = refundMapper.selectByAppIdAndMerchantRefundId( - app.getId(), reqDTO.getMerchantRefundId()); - if (refund != null) { - throw exception(REFUND_EXISTS); - } - - // 2.1 插入退款单 - String no = noRedisDAO.generate(payProperties.getRefundNoPrefix()); - refund = PayRefundConvert.INSTANCE.convert(reqDTO) - .setNo(no).setAppId(app.getId()).setOrderId(order.getId()).setOrderNo(order.getNo()) - .setChannelId(order.getChannelId()).setChannelCode(order.getChannelCode()) - // 商户相关的字段 - .setNotifyUrl(app.getRefundNotifyUrl()) - // 渠道相关字段 - .setChannelOrderNo(order.getChannelOrderNo()) - // 退款相关字段 - .setStatus(PayRefundStatusEnum.WAITING.getStatus()) - .setPayPrice(order.getPrice()).setRefundPrice(reqDTO.getPrice()); - refundMapper.insert(refund); - try { - // 2.2 向渠道发起退款申请 - PayRefundUnifiedReqDTO unifiedReqDTO = new PayRefundUnifiedReqDTO() - .setPayPrice(order.getPrice()) - .setRefundPrice(reqDTO.getPrice()) - .setOutTradeNo(order.getNo()) - .setOutRefundNo(refund.getNo()) - .setNotifyUrl(genChannelRefundNotifyUrl(channel)) - .setReason(reqDTO.getReason()); - PayRefundRespDTO refundRespDTO = client.unifiedRefund(unifiedReqDTO); - // 2.3 处理退款返回 - getSelf().notifyRefund(channel, refundRespDTO); - } catch (Throwable e) { - // 注意:这里仅打印异常,不进行抛出。 - // 原因是:虽然调用支付渠道进行退款发生异常(网络请求超时),实际退款成功。这个结果,后续通过退款回调、或者退款轮询补偿可以拿到。 - // 最终,在异常的情况下,支付中心会异步回调业务的退款回调接口,提供退款结果 - log.error("[createPayRefund][退款 id({}) requestDTO({}) 发生异常]", - refund.getId(), reqDTO, e); - } - - // 返回退款编号 - return refund.getId(); - } - - /** - * 校验支付订单是否可以退款 - * - * @param reqDTO 退款申请信息 - * @return 支付订单 - */ - private PayOrderDO validatePayOrderCanRefund(PayRefundCreateReqDTO reqDTO, Long appId) { - PayOrderDO order = orderService.getOrder(appId, reqDTO.getMerchantOrderId()); - if (order == null) { - throw exception(PAY_ORDER_NOT_FOUND); - } - // 校验状态,必须是已支付、或者已退款 - if (!PayOrderStatusEnum.isSuccessOrRefund(order.getStatus())) { - throw exception(PAY_ORDER_REFUND_FAIL_STATUS_ERROR); - } - - // 校验金额,退款金额不能大于原定的金额 - if (reqDTO.getPrice() + order.getRefundPrice() > order.getPrice()) { - throw exception(REFUND_PRICE_EXCEED); - } - // 是否有退款中的订单 - if (refundMapper.selectCountByAppIdAndOrderId(appId, order.getId(), - PayRefundStatusEnum.WAITING.getStatus()) > 0) { - throw exception(REFUND_HAS_REFUNDING); - } - return order; - } - - /** - * 根据支付渠道的编码,生成支付渠道的回调地址 - * - * @param channel 支付渠道 - * @return 支付渠道的回调地址 配置地址 + "/" + channel id - */ - private String genChannelRefundNotifyUrl(PayChannelDO channel) { - return payProperties.getRefundNotifyUrl() + "/" + channel.getId(); - } - - @Override - public void notifyRefund(Long channelId, PayRefundRespDTO notify) { - // 校验支付渠道是否有效 - PayChannelDO channel = channelService.validPayChannel(channelId); - // 更新退款订单 - TenantUtils.execute(channel.getTenantId(), () -> getSelf().notifyRefund(channel, notify)); - } - - /** - * 通知并更新订单的退款结果 - * - * @param channel 支付渠道 - * @param notify 通知 - */ - // 注意,如果是方法内调用该方法,需要通过 getSelf().notifyRefund(channel, notify) 调用,否则事务不生效 - @Transactional(rollbackFor = Exception.class) - public void notifyRefund(PayChannelDO channel, PayRefundRespDTO notify) { - // 情况一:退款成功 - if (PayRefundStatusEnum.isSuccess(notify.getStatus())) { - notifyRefundSuccess(channel, notify); - return; - } - // 情况二:退款失败 - if (PayRefundStatusEnum.isFailure(notify.getStatus())) { - notifyRefundFailure(channel, notify); - } - } - - private void notifyRefundSuccess(PayChannelDO channel, PayRefundRespDTO notify) { - // 1.1 查询 PayRefundDO - PayRefundDO refund = refundMapper.selectByAppIdAndNo( - channel.getAppId(), notify.getOutRefundNo()); - if (refund == null) { - throw exception(REFUND_NOT_FOUND); - } - if (PayRefundStatusEnum.isSuccess(refund.getStatus())) { // 如果已经是成功,直接返回,不用重复更新 - log.info("[notifyRefundSuccess][退款订单({}) 已经是退款成功,无需更新]", refund.getId()); - return; - } - if (!PayRefundStatusEnum.WAITING.getStatus().equals(refund.getStatus())) { - throw exception(REFUND_STATUS_IS_NOT_WAITING); - } - // 1.2 更新 PayRefundDO - PayRefundDO updateRefundObj = new PayRefundDO() - .setSuccessTime(notify.getSuccessTime()) - .setChannelRefundNo(notify.getChannelRefundNo()) - .setStatus(PayRefundStatusEnum.SUCCESS.getStatus()) - .setChannelNotifyData(toJsonString(notify)); - int updateCounts = refundMapper.updateByIdAndStatus(refund.getId(), refund.getStatus(), updateRefundObj); - if (updateCounts == 0) { // 校验状态,必须是等待状态 - throw exception(REFUND_STATUS_IS_NOT_WAITING); - } - log.info("[notifyRefundSuccess][退款订单({}) 更新为退款成功]", refund.getId()); - - // 2. 更新订单 - orderService.updateOrderRefundPrice(refund.getOrderId(), refund.getRefundPrice()); - - // 3. 插入退款通知记录 - notifyService.createPayNotifyTask(PayNotifyTypeEnum.REFUND.getType(), - refund.getId()); - } - - private void notifyRefundFailure(PayChannelDO channel, PayRefundRespDTO notify) { - // 1.1 查询 PayRefundDO - PayRefundDO refund = refundMapper.selectByAppIdAndNo( - channel.getAppId(), notify.getOutRefundNo()); - if (refund == null) { - throw exception(REFUND_NOT_FOUND); - } - if (PayRefundStatusEnum.isFailure(refund.getStatus())) { // 如果已经是成功,直接返回,不用重复更新 - log.info("[notifyRefundSuccess][退款订单({}) 已经是退款关闭,无需更新]", refund.getId()); - return; - } - if (!PayRefundStatusEnum.WAITING.getStatus().equals(refund.getStatus())) { - throw exception(REFUND_STATUS_IS_NOT_WAITING); - } - // 1.2 更新 PayRefundDO - PayRefundDO updateRefundObj = new PayRefundDO() - .setChannelRefundNo(notify.getChannelRefundNo()) - .setStatus(PayRefundStatusEnum.FAILURE.getStatus()) - .setChannelNotifyData(toJsonString(notify)) - .setChannelErrorCode(notify.getChannelErrorCode()).setChannelErrorMsg(notify.getChannelErrorMsg()); - int updateCounts = refundMapper.updateByIdAndStatus(refund.getId(), refund.getStatus(), updateRefundObj); - if (updateCounts == 0) { // 校验状态,必须是等待状态 - throw exception(REFUND_STATUS_IS_NOT_WAITING); - } - log.info("[notifyRefundFailure][退款订单({}) 更新为退款失败]", refund.getId()); - - // 2. 插入退款通知记录 - notifyService.createPayNotifyTask(PayNotifyTypeEnum.REFUND.getType(), - refund.getId()); - } - - @Override - public int syncRefund() { - // 1. 查询指定创建时间内的待退款订单 - List refunds = refundMapper.selectListByStatus(PayRefundStatusEnum.WAITING.getStatus()); - if (CollUtil.isEmpty(refunds)) { - return 0; - } - // 2. 遍历执行 - int count = 0; - for (PayRefundDO refund : refunds) { - count += syncRefund(refund) ? 1 : 0; - } - return count; - } - - /** - * 同步单个退款订单 - * - * @param refund 退款订单 - * @return 是否同步到 - */ - private boolean syncRefund(PayRefundDO refund) { - try { - // 1.1 查询退款订单信息 - PayClient payClient = channelService.getPayClient(refund.getChannelId()); - if (payClient == null) { - log.error("[syncRefund][渠道编号({}) 找不到对应的支付客户端]", refund.getChannelId()); - return false; - } - PayRefundRespDTO respDTO = payClient.getRefund(refund.getOrderNo(), refund.getNo()); - // 1.2 回调退款结果 - notifyRefund(refund.getChannelId(), respDTO); - - // 2. 如果同步到,则返回 true - return PayRefundStatusEnum.isSuccess(respDTO.getStatus()) - || PayRefundStatusEnum.isFailure(respDTO.getStatus()); - } catch (Throwable e) { - log.error("[syncRefund][refund({}) 同步退款状态异常]", refund.getId(), e); - return false; - } - } - - /** - * 获得自身的代理对象,解决 AOP 生效问题 - * - * @return 自己 - */ - private PayRefundServiceImpl getSelf() { - return SpringUtil.getBean(getClass()); - } - -} 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 deleted file mode 100644 index e2a1dbd..0000000 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/transfer/PayTransferService.java +++ /dev/null @@ -1,70 +0,0 @@ -package cn.iocoder.yudao.module.pay.service.transfer; - -import cn.iocoder.yudao.framework.common.pojo.PageResult; -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; -import cn.iocoder.yudao.module.pay.dal.dataobject.transfer.PayTransferDO; -import jakarta.validation.Valid; - -/** - * 转账 Service 接口 - * - * @author jason - */ -public interface PayTransferService { - - /** - * 创建转账单,并发起转账 - * - * @param reqDTO 创建请求 - * @return 转账单编号 - */ - PayTransferCreateRespDTO createTransfer(@Valid PayTransferCreateReqDTO reqDTO); - - /** - * 获取转账单 - * @param id 转账单编号 - */ - PayTransferDO getTransfer(Long id); - - /** - * 根据转账单号获取转账单 - * - * @param no 转账单号 - * @return 转账单 - */ - PayTransferDO getTransferByNo(String no); - - /** - * 获得转账单分页 - * - * @param pageReqVO 分页查询 - * @return 转账单分页 - */ - PageResult getTransferPage(PayTransferPageReqVO pageReqVO); - - /** - * 同步渠道转账单状态 - * - * @return 同步到状态的转账数量,包括转账成功、转账失败、转账中的 - */ - int syncTransfer(); - - /** - * 【单个】同步渠道转账单状态 - * - * @param id 转账单编号 - */ - void syncTransfer(Long id); - - /** - * 渠道的转账通知 - * - * @param channelId 渠道编号 - * @param notify 通知 - */ - void notifyTransfer(Long channelId, PayTransferRespDTO notify); - -} 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 deleted file mode 100644 index dbd12c7..0000000 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/transfer/PayTransferServiceImpl.java +++ /dev/null @@ -1,319 +0,0 @@ -package cn.iocoder.yudao.module.pay.service.transfer; - -import cn.hutool.core.collection.CollUtil; -import cn.hutool.core.util.ObjectUtil; -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.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; -import cn.iocoder.yudao.module.pay.controller.admin.transfer.vo.PayTransferPageReqVO; -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.transfer.PayTransferDO; -import cn.iocoder.yudao.module.pay.dal.mysql.transfer.PayTransferMapper; -import cn.iocoder.yudao.module.pay.dal.redis.no.PayNoRedisDAO; -import cn.iocoder.yudao.module.pay.enums.notify.PayNotifyTypeEnum; -import cn.iocoder.yudao.module.pay.enums.transfer.PayTransferStatusEnum; -import cn.iocoder.yudao.module.pay.framework.pay.config.PayProperties; -import cn.iocoder.yudao.module.pay.service.app.PayAppService; -import cn.iocoder.yudao.module.pay.service.channel.PayChannelService; -import cn.iocoder.yudao.module.pay.service.notify.PayNotifyService; -import jakarta.annotation.Resource; -import lombok.extern.slf4j.Slf4j; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - -import java.util.List; - -import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; -import static cn.iocoder.yudao.module.pay.enums.ErrorCodeConstants.*; - -// TODO @jason:等彻底实现完,单测写写; - -/** - * 转账 Service 实现类 - * - * @author jason - */ -@Service -@Slf4j -public class PayTransferServiceImpl implements PayTransferService { - - private static final String TRANSFER_NO_PREFIX = "T"; - - @Resource - private PayProperties payProperties; - - @Resource - private PayTransferMapper transferMapper; - @Resource - private PayAppService appService; - @Resource - private PayChannelService channelService; - @Resource - private PayNotifyService notifyService; - @Resource - private PayNoRedisDAO noRedisDAO; - - @Override - public PayTransferCreateRespDTO createTransfer(PayTransferCreateReqDTO reqDTO) { - // 1.1 校验 App - PayAppDO payApp = appService.validPayApp(reqDTO.getAppKey()); - // 1.2 校验支付渠道是否有效 - PayChannelDO channel = channelService.validPayChannel(payApp.getId(), reqDTO.getChannelCode()); - PayClient client = channelService.getPayClient(channel.getId()); - if (client == null) { - log.error("[createTransfer][渠道编号({}) 找不到对应的支付客户端]", channel.getId()); - throw exception(CHANNEL_NOT_FOUND); - } - // 1.3 校验转账单已经发起过转账 - PayTransferDO transfer = validateTransferCanCreate(reqDTO, payApp.getId()); - - // 2.1 情况一:不存在创建转账单,则进行创建 - if (transfer == null) { - String no = noRedisDAO.generate(TRANSFER_NO_PREFIX); - transfer = BeanUtils.toBean(reqDTO, PayTransferDO.class) - .setAppId(channel.getAppId()).setChannelId(channel.getId()) - .setNo(no).setStatus(PayTransferStatusEnum.WAITING.getStatus()) - .setNotifyUrl(payApp.getTransferNotifyUrl()); - transferMapper.insert(transfer); - } else { - // 2.2 情况二:存在创建转账单,但是状态为关闭,则更新为等待中 - transferMapper.updateByIdAndStatus(transfer.getId(), transfer.getStatus(), - new PayTransferDO().setStatus(PayTransferStatusEnum.WAITING.getStatus())); - } - PayTransferRespDTO unifiedTransferResp = null; - try { - // 3. 调用三方渠道发起转账 - PayTransferUnifiedReqDTO transferUnifiedReq = BeanUtils.toBean(reqDTO, PayTransferUnifiedReqDTO.class) - .setOutTransferNo(transfer.getNo()) - .setNotifyUrl(genChannelTransferNotifyUrl(channel)); - unifiedTransferResp = client.unifiedTransfer(transferUnifiedReq); - // 4. 通知转账结果 - getSelf().notifyTransfer(channel, unifiedTransferResp); - } catch (Throwable e) { - // 注意这里仅打印异常,不进行抛出。 - // 原因是:虽然调用支付渠道进行转账发生异常(网络请求超时),实际转账成功。这个结果,后续转账轮询可以拿到。 - // 或者,使用相同 no 再次发起转账请求 - log.error("[createTransfer][转账编号({}) requestDTO({}) 发生异常]", transfer.getId(), reqDTO, e); - } - return new PayTransferCreateRespDTO().setId(transfer.getId()) - .setChannelPackageInfo(unifiedTransferResp != null ? unifiedTransferResp.getChannelPackageInfo() : null); - } - - /** - * 根据支付渠道的编码,生成支付渠道的回调地址 - * - * @param channel 支付渠道 - * @return 支付渠道的回调地址 配置地址 + "/" + channel id - */ - private String genChannelTransferNotifyUrl(PayChannelDO channel) { - return payProperties.getTransferNotifyUrl() + "/" + channel.getId(); - } - - private PayTransferDO validateTransferCanCreate(PayTransferCreateReqDTO reqDTO, Long appId) { - PayTransferDO transfer = transferMapper.selectByAppIdAndMerchantOrderId(appId, reqDTO.getMerchantTransferId()); - if (transfer != null) { - // 只有转账单状态为关闭,才能再次发起转账 - if (!PayTransferStatusEnum.isClosed(transfer.getStatus())) { - throw exception(PAY_TRANSFER_CREATE_FAIL_STATUS_NOT_CLOSED); - } - // 校验参数是否一致 - if (ObjectUtil.notEqual(reqDTO.getPrice(), transfer.getPrice())) { - throw exception(PAY_TRANSFER_CREATE_PRICE_NOT_MATCH); - } - if (ObjectUtil.notEqual(reqDTO.getChannelCode(), transfer.getChannelCode())) { - throw exception(PAY_TRANSFER_CREATE_CHANNEL_NOT_MATCH); - } - } - // 如果状态为等待状态:不知道渠道转账是否发起成功 - // 特殊:允许使用相同的 no 再次发起转账,渠道会保证幂等 - return transfer; - } - - @Transactional(rollbackFor = Exception.class) - // 注意,如果是方法内调用该方法,需要通过 getSelf().notifyTransfer(channel, notify) 调用,否则事务不生效 - public void notifyTransfer(PayChannelDO channel, PayTransferRespDTO notify) { - // 转账成功的回调 - if (PayTransferStatusEnum.isSuccess(notify.getStatus())) { - notifyTransferSuccess(channel, notify); - } - // 转账关闭的回调 - if (PayTransferStatusEnum.isClosed(notify.getStatus())) { - notifyTransferClosed(channel, notify); - } - // 转账处理中的回调 - if (PayTransferStatusEnum.isProcessing(notify.getStatus())) { - notifyTransferProgressing(channel, notify); - } - // WAITING 状态无需处理 - } - - private void notifyTransferProgressing(PayChannelDO channel, PayTransferRespDTO notify) { - // 1. 校验 - PayTransferDO transfer = transferMapper.selectByAppIdAndNo(channel.getAppId(), notify.getOutTransferNo()); - if (transfer == null) { - throw exception(PAY_TRANSFER_NOT_FOUND); - } - if (PayTransferStatusEnum.isProcessing(transfer.getStatus())) { // 如果已经是转账中,直接返回,不用重复更新 - log.info("[notifyTransferProgressing][transfer({}) 已经是转账中状态,无需更新]", transfer.getId()); - return; - } - if (!PayTransferStatusEnum.isWaiting(transfer.getStatus())) { - throw exception(PAY_TRANSFER_NOTIFY_FAIL_STATUS_IS_NOT_WAITING); - } - - // 2. 更新状态 - int updateCounts = transferMapper.updateByIdAndStatus(transfer.getId(), - PayTransferStatusEnum.WAITING.getStatus(), - new PayTransferDO().setStatus(PayTransferStatusEnum.PROCESSING.getStatus()) - .setChannelPackageInfo(notify.getChannelPackageInfo())); - if (updateCounts == 0) { - throw exception(PAY_TRANSFER_NOTIFY_FAIL_STATUS_IS_NOT_WAITING); - } - log.info("[notifyTransferProgressing][transfer({}) 更新为转账进行中状态]", transfer.getId()); - } - - private void notifyTransferSuccess(PayChannelDO channel, PayTransferRespDTO notify) { - // 1. 校验状态 - PayTransferDO transfer = transferMapper.selectByAppIdAndNo(channel.getAppId(), notify.getOutTransferNo()); - if (transfer == null) { - throw exception(PAY_TRANSFER_NOT_FOUND); - } - if (PayTransferStatusEnum.isSuccess(transfer.getStatus())) { // 如果已成功,直接返回,不用重复更新 - log.info("[notifyTransferSuccess][transfer({}) 已经是成功状态,无需更新]", transfer.getId()); - return; - } - if (!PayTransferStatusEnum.isWaitingOrProcessing(transfer.getStatus())) { - throw exception(PAY_TRANSFER_NOTIFY_FAIL_STATUS_NOT_WAITING_OR_PROCESSING); - } - - // 2. 更新状态 - int updateCounts = transferMapper.updateByIdAndStatus(transfer.getId(), - CollUtil.newArrayList(PayTransferStatusEnum.WAITING.getStatus(), PayTransferStatusEnum.PROCESSING.getStatus()), - new PayTransferDO().setStatus(PayTransferStatusEnum.SUCCESS.getStatus()) - .setSuccessTime(notify.getSuccessTime()) - .setChannelTransferNo(notify.getChannelTransferNo()) - .setChannelNotifyData(JsonUtils.toJsonString(notify))); - if (updateCounts == 0) { - throw exception(PAY_TRANSFER_NOTIFY_FAIL_STATUS_NOT_WAITING_OR_PROCESSING); - } - log.info("[notifyTransferSuccess][transfer({}) 更新为已转账]", transfer.getId()); - - // 3. 插入转账通知记录 - notifyService.createPayNotifyTask(PayNotifyTypeEnum.TRANSFER.getType(), transfer.getId()); - } - - private void notifyTransferClosed(PayChannelDO channel, PayTransferRespDTO notify) { - // 1. 校验状态 - PayTransferDO transfer = transferMapper.selectByAppIdAndNo(channel.getAppId(), notify.getOutTransferNo()); - if (transfer == null) { - throw exception(PAY_TRANSFER_NOT_FOUND); - } - if (PayTransferStatusEnum.isClosed(transfer.getStatus())) { // 如果已是关闭状态,直接返回,不用重复更新 - log.info("[notifyTransferClosed][transfer({}) 已经是关闭状态,无需更新]", transfer.getId()); - return; - } - if (!PayTransferStatusEnum.isWaitingOrProcessing(transfer.getStatus())) { - throw exception(PAY_TRANSFER_NOTIFY_FAIL_STATUS_NOT_WAITING_OR_PROCESSING); - } - - // 2. 更新状态 - int updateCount = transferMapper.updateByIdAndStatus(transfer.getId(), - CollUtil.newArrayList(PayTransferStatusEnum.WAITING.getStatus(), PayTransferStatusEnum.PROCESSING.getStatus()), - new PayTransferDO().setStatus(PayTransferStatusEnum.CLOSED.getStatus()) - .setChannelTransferNo(notify.getChannelTransferNo()) - .setChannelNotifyData(JsonUtils.toJsonString(notify)) - .setChannelErrorCode(notify.getChannelErrorCode()).setChannelErrorMsg(notify.getChannelErrorMsg())); - if (updateCount == 0) { - throw exception(PAY_TRANSFER_NOTIFY_FAIL_STATUS_NOT_WAITING_OR_PROCESSING); - } - log.info("[notifyTransferClosed][transfer({}) 更新为关闭状态]", transfer.getId()); - - // 3. 插入转账通知记录 - notifyService.createPayNotifyTask(PayNotifyTypeEnum.TRANSFER.getType(), transfer.getId()); - } - - @Override - public PayTransferDO getTransfer(Long id) { - return transferMapper.selectById(id); - } - - @Override - public PayTransferDO getTransferByNo(String no) { - return transferMapper.selectByNo(no); - } - - @Override - public PageResult getTransferPage(PayTransferPageReqVO pageReqVO) { - return transferMapper.selectPage(pageReqVO); - } - - @Override - public int syncTransfer() { - List list = transferMapper.selectListByStatus(CollUtil.newArrayList( - PayTransferStatusEnum.WAITING.getStatus(), PayTransferStatusEnum.PROCESSING.getStatus())); - if (CollUtil.isEmpty(list)) { - return 0; - } - int count = 0; - for (PayTransferDO transfer : list) { - if (!transfer.getId().equals(54L)) { - continue; - } - count += syncTransfer(transfer) ? 1 : 0; - } - return count; - } - - @Override - public void syncTransfer(Long id) { - PayTransferDO transfer = transferMapper.selectById(id); - if (transfer == null) { - throw exception(PAY_TRANSFER_NOT_FOUND); - } - syncTransfer(transfer); - } - - private boolean syncTransfer(PayTransferDO transfer) { - try { - // 1. 查询转账订单信息 - PayClient payClient = channelService.getPayClient(transfer.getChannelId()); - if (payClient == null) { - log.error("[syncTransfer][渠道编号({}) 找不到对应的支付客户端]", transfer.getChannelId()); - return false; - } - PayTransferRespDTO resp = payClient.getTransfer(transfer.getNo()); - - // 2. 回调转账结果 - notifyTransfer(transfer.getChannelId(), resp); - return true; - } catch (Throwable ex) { - log.error("[syncTransfer][transfer({}) 同步转账单状态异常]", transfer.getId(), ex); - return false; - } - } - - public void notifyTransfer(Long channelId, PayTransferRespDTO notify) { - // 校验渠道是否有效 - PayChannelDO channel = channelService.validPayChannel(channelId); - // 通知转账结果给对应的业务 - TenantUtils.execute(channel.getTenantId(), () -> getSelf().notifyTransfer(channel, notify)); - } - - /** - * 获得自身的代理对象,解决 AOP 生效问题 - * - * @return 自己 - */ - private PayTransferServiceImpl getSelf() { - return SpringUtil.getBean(getClass()); - } - -} diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/wallet/PayWalletRechargePackageService.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/wallet/PayWalletRechargePackageService.java deleted file mode 100644 index 06e4632..0000000 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/wallet/PayWalletRechargePackageService.java +++ /dev/null @@ -1,71 +0,0 @@ -package cn.iocoder.yudao.module.pay.service.wallet; - -import cn.iocoder.yudao.framework.common.pojo.PageResult; -import cn.iocoder.yudao.module.pay.controller.admin.wallet.vo.rechargepackage.WalletRechargePackageCreateReqVO; -import cn.iocoder.yudao.module.pay.controller.admin.wallet.vo.rechargepackage.WalletRechargePackagePageReqVO; -import cn.iocoder.yudao.module.pay.controller.admin.wallet.vo.rechargepackage.WalletRechargePackageUpdateReqVO; -import cn.iocoder.yudao.module.pay.dal.dataobject.wallet.PayWalletRechargePackageDO; - -import jakarta.validation.Valid; -import java.util.Collection; -import java.util.List; - -/** - * 钱包充值套餐 Service 接口 - * - * @author jason - */ -public interface PayWalletRechargePackageService { - - /** - * 获取钱包充值套餐 - * @param packageId 充值套餐编号 - */ - PayWalletRechargePackageDO getWalletRechargePackage(Long packageId); - - /** - * 校验钱包充值套餐的有效性, 无效的话抛出 ServiceException 异常 - * - * @param packageId 充值套餐编号 - */ - PayWalletRechargePackageDO validWalletRechargePackage(Long packageId); - - /** - * 创建充值套餐 - * - * @param createReqVO 创建信息 - * @return 编号 - */ - Long createWalletRechargePackage(@Valid WalletRechargePackageCreateReqVO createReqVO); - - /** - * 更新充值套餐 - * - * @param updateReqVO 更新信息 - */ - void updateWalletRechargePackage(@Valid WalletRechargePackageUpdateReqVO updateReqVO); - - /** - * 删除充值套餐 - * - * @param id 编号 - */ - void deleteWalletRechargePackage(Long id); - - /** - * 获得充值套餐分页 - * - * @param pageReqVO 分页查询 - * @return 充值套餐分页 - */ - PageResult getWalletRechargePackagePage(WalletRechargePackagePageReqVO pageReqVO); - - /** - * 获得充值套餐列表 - * - * @param status 状态 - * @return 充值套餐列表 - */ - List getWalletRechargePackageList(Integer status); - -} diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/wallet/PayWalletRechargePackageServiceImpl.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/wallet/PayWalletRechargePackageServiceImpl.java deleted file mode 100644 index 82fa676..0000000 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/wallet/PayWalletRechargePackageServiceImpl.java +++ /dev/null @@ -1,113 +0,0 @@ -package cn.iocoder.yudao.module.pay.service.wallet; - -import cn.hutool.core.util.StrUtil; -import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; -import cn.iocoder.yudao.framework.common.pojo.PageResult; -import cn.iocoder.yudao.module.pay.controller.admin.wallet.vo.rechargepackage.WalletRechargePackageCreateReqVO; -import cn.iocoder.yudao.module.pay.controller.admin.wallet.vo.rechargepackage.WalletRechargePackagePageReqVO; -import cn.iocoder.yudao.module.pay.controller.admin.wallet.vo.rechargepackage.WalletRechargePackageUpdateReqVO; -import cn.iocoder.yudao.module.pay.convert.wallet.PayWalletRechargePackageConvert; -import cn.iocoder.yudao.module.pay.dal.dataobject.wallet.PayWalletRechargePackageDO; -import cn.iocoder.yudao.module.pay.dal.mysql.wallet.PayWalletRechargePackageMapper; -import org.springframework.stereotype.Service; - -import jakarta.annotation.Resource; - -import java.util.List; - -import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; -import static cn.iocoder.yudao.module.pay.enums.ErrorCodeConstants.*; - -/** - * 钱包充值套餐 Service 实现类 - * - * @author jason - */ -@Service -public class PayWalletRechargePackageServiceImpl implements PayWalletRechargePackageService { - - @Resource - private PayWalletRechargePackageMapper walletRechargePackageMapper; - - @Override - public PayWalletRechargePackageDO getWalletRechargePackage(Long packageId) { - return walletRechargePackageMapper.selectById(packageId); - } - - @Override - public PayWalletRechargePackageDO validWalletRechargePackage(Long packageId) { - PayWalletRechargePackageDO rechargePackageDO = walletRechargePackageMapper.selectById(packageId); - if (rechargePackageDO == null) { - throw exception(WALLET_RECHARGE_PACKAGE_NOT_FOUND); - } - if (CommonStatusEnum.DISABLE.getStatus().equals(rechargePackageDO.getStatus())) { - throw exception(WALLET_RECHARGE_PACKAGE_IS_DISABLE); - } - return rechargePackageDO; - } - - @Override - public Long createWalletRechargePackage(WalletRechargePackageCreateReqVO createReqVO) { - // 校验套餐名是否唯一 - validateRechargePackageNameUnique(null, createReqVO.getName()); - - // 插入 - PayWalletRechargePackageDO walletRechargePackage = PayWalletRechargePackageConvert.INSTANCE.convert(createReqVO); - walletRechargePackageMapper.insert(walletRechargePackage); - // 返回 - return walletRechargePackage.getId(); - } - - @Override - public void updateWalletRechargePackage(WalletRechargePackageUpdateReqVO updateReqVO) { - // 校验存在 - validateWalletRechargePackageExists(updateReqVO.getId()); - // 校验套餐名是否唯一 - validateRechargePackageNameUnique(updateReqVO.getId(), updateReqVO.getName()); - - // 更新 - PayWalletRechargePackageDO updateObj = PayWalletRechargePackageConvert.INSTANCE.convert(updateReqVO); - walletRechargePackageMapper.updateById(updateObj); - } - - private void validateRechargePackageNameUnique(Long id, String name) { - if (StrUtil.isBlank(name)) { - return; - } - PayWalletRechargePackageDO rechargePackage = walletRechargePackageMapper.selectByName(name); - if (rechargePackage == null) { - return ; - } - if (id == null) { - throw exception(WALLET_RECHARGE_PACKAGE_NAME_EXISTS); - } - if (!id.equals(rechargePackage.getId())) { - throw exception(WALLET_RECHARGE_PACKAGE_NAME_EXISTS); - } - } - - @Override - public void deleteWalletRechargePackage(Long id) { - // 校验存在 - validateWalletRechargePackageExists(id); - // 删除 - walletRechargePackageMapper.deleteById(id); - } - - private void validateWalletRechargePackageExists(Long id) { - if (walletRechargePackageMapper.selectById(id) == null) { - throw exception(WALLET_RECHARGE_PACKAGE_NOT_FOUND); - } - } - - @Override - public PageResult getWalletRechargePackagePage(WalletRechargePackagePageReqVO pageReqVO) { - return walletRechargePackageMapper.selectPage(pageReqVO); - } - - @Override - public List getWalletRechargePackageList(Integer status) { - return walletRechargePackageMapper.selectListByStatus(status); - } - -} diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/wallet/PayWalletRechargeService.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/wallet/PayWalletRechargeService.java deleted file mode 100644 index f2ab50e..0000000 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/wallet/PayWalletRechargeService.java +++ /dev/null @@ -1,64 +0,0 @@ -package cn.iocoder.yudao.module.pay.service.wallet; - -import cn.iocoder.yudao.framework.common.pojo.PageParam; -import cn.iocoder.yudao.framework.common.pojo.PageResult; -import cn.iocoder.yudao.module.pay.controller.app.wallet.vo.recharge.AppPayWalletRechargeCreateReqVO; -import cn.iocoder.yudao.module.pay.dal.dataobject.wallet.PayWalletRechargeDO; - -/** - * 钱包充值 Service 接口 - * - * @author jason - */ -public interface PayWalletRechargeService { - - /** - * 创建钱包充值记录(发起充值) - * - * @param userId 用户编号 - * @param userType 用户类型 - * @param createReqVO 钱包充值请求 VO - * @param userIp 用户Ip - * @return 钱包充值记录 - */ - PayWalletRechargeDO createWalletRecharge(Long userId, Integer userType, String userIp, - AppPayWalletRechargeCreateReqVO createReqVO); - - /** - * 获得钱包充值记录分页 - * - * @param userId 用户编号 - * @param userType 用户类型 - * @param pageReqVO 分页请求 - * @param payStatus 是否支付 - * @return 钱包充值记录分页 - */ - PageResult getWalletRechargePackagePage(Long userId, Integer userType, - PageParam pageReqVO, Boolean payStatus); - - /** - * 更新钱包充值成功 - * - * @param id 钱包充值记录编号 - * @param payOrderId 支付订单编号 - */ - void updateWalletRechargerPaid(Long id, Long payOrderId); - - /** - * 发起钱包充值退款 - * - * @param id 钱包充值编号 - * @param userIp 用户 ip 地址 - */ - void refundWalletRecharge(Long id, String userIp); - - /** - * 更新钱包充值记录为已退款 - * - * @param id 钱包充值记录编号 - * @param refundId 钱包充值退款编号(实际和 id 相同) - * @param payRefundId 退款单id - */ - void updateWalletRechargeRefunded(Long id, Long refundId, Long payRefundId); - -} 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 deleted file mode 100644 index e4cda98..0000000 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/wallet/PayWalletRechargeServiceImpl.java +++ /dev/null @@ -1,331 +0,0 @@ -package cn.iocoder.yudao.module.pay.service.wallet; - -import cn.hutool.core.date.LocalDateTimeUtil; -import cn.hutool.core.lang.Assert; -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.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; -import cn.iocoder.yudao.module.pay.api.refund.dto.PayRefundRespDTO; -import cn.iocoder.yudao.module.pay.controller.app.wallet.vo.recharge.AppPayWalletRechargeCreateReqVO; -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.dal.dataobject.wallet.PayWalletRechargeDO; -import cn.iocoder.yudao.module.pay.dal.dataobject.wallet.PayWalletRechargePackageDO; -import cn.iocoder.yudao.module.pay.dal.mysql.wallet.PayWalletRechargeMapper; -import cn.iocoder.yudao.module.pay.enums.order.PayOrderStatusEnum; -import cn.iocoder.yudao.module.pay.enums.refund.PayRefundStatusEnum; -import cn.iocoder.yudao.module.pay.enums.wallet.PayWalletBizTypeEnum; -import cn.iocoder.yudao.module.pay.framework.pay.config.PayProperties; -import cn.iocoder.yudao.module.pay.service.order.PayOrderService; -import cn.iocoder.yudao.module.system.api.social.SocialClientApi; -import cn.iocoder.yudao.module.system.api.social.dto.SocialWxaSubscribeMessageSendReqDTO; -import jakarta.annotation.Resource; -import lombok.extern.slf4j.Slf4j; -import org.springframework.scheduling.annotation.Async; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - -import java.time.Duration; -import java.time.LocalDateTime; -import java.util.Objects; - -import static cn.hutool.core.util.ObjectUtil.notEqual; -import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; -import static cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.addTime; -import static cn.iocoder.yudao.framework.common.util.json.JsonUtils.toJsonString; -import static cn.iocoder.yudao.framework.common.util.number.MoneyUtils.fenToYuanStr; -import static cn.iocoder.yudao.module.pay.convert.wallet.PayWalletRechargeConvert.INSTANCE; -import static cn.iocoder.yudao.module.pay.enums.ErrorCodeConstants.*; -import static cn.iocoder.yudao.module.pay.enums.MessageTemplateConstants.WXA_WALLET_RECHARGER_PAID; -import static cn.iocoder.yudao.module.pay.enums.refund.PayRefundStatusEnum.*; - -/** - * 钱包充值 Service 实现类 - * - * @author jason - */ -@Service -@Slf4j -public class PayWalletRechargeServiceImpl implements PayWalletRechargeService { - - private static final String WALLET_RECHARGE_ORDER_SUBJECT = "钱包余额充值"; - - @Resource - private PayWalletRechargeMapper walletRechargeMapper; - @Resource - private PayWalletService payWalletService; - @Resource - private PayOrderService payOrderService; -// @Resource -// private PayRefundService payRefundService; - @Resource - private PayWalletRechargePackageService payWalletRechargePackageService; - - @Resource - public SocialClientApi socialClientApi; - @Resource - private PayRefundApi payRefundApi; - - @Resource - private PayProperties payProperties; - - @Override - @Transactional(rollbackFor = Exception.class) - public PayWalletRechargeDO createWalletRecharge(Long userId, Integer userType, String userIp, - AppPayWalletRechargeCreateReqVO reqVO) { - // 1.1 计算充值金额 - int payPrice; - int bonusPrice = 0; - if (Objects.nonNull(reqVO.getPackageId())) { - PayWalletRechargePackageDO rechargePackage = payWalletRechargePackageService.validWalletRechargePackage(reqVO.getPackageId()); - payPrice = rechargePackage.getPayPrice(); - bonusPrice = rechargePackage.getBonusPrice(); - } else { - payPrice = reqVO.getPayPrice(); - } - // 1.2 插入充值记录 - PayWalletDO wallet = payWalletService.getOrCreateWallet(userId, userType); - PayWalletRechargeDO recharge = INSTANCE.convert(wallet.getId(), payPrice, bonusPrice, reqVO.getPackageId()); - walletRechargeMapper.insert(recharge); - - // 2.1 创建支付单 - Long payOrderId = payOrderService.createOrder(new PayOrderCreateReqDTO() - .setAppKey(payProperties.getWalletPayAppKey()).setUserIp(userIp) - .setMerchantOrderId(recharge.getId().toString()) // 业务的订单编号 - .setSubject(WALLET_RECHARGE_ORDER_SUBJECT).setBody("") - .setPrice(recharge.getPayPrice()) - .setExpireTime(addTime(Duration.ofHours(2L)))); // TODO @芋艿:支付超时时间 - // 2.2 更新钱包充值记录中支付订单 - walletRechargeMapper.updateById(new PayWalletRechargeDO().setId(recharge.getId()).setPayOrderId(payOrderId)); - recharge.setPayOrderId(payOrderId); - return recharge; - } - - @Override - public PageResult getWalletRechargePackagePage(Long userId, Integer userType, - PageParam pageReqVO, Boolean payStatus) { - PayWalletDO wallet = payWalletService.getOrCreateWallet(userId, userType); - return walletRechargeMapper.selectPage(pageReqVO, wallet.getId(), payStatus); - } - - @Override - @Transactional(rollbackFor = Exception.class) - public void updateWalletRechargerPaid(Long id, Long payOrderId) { - // 1.1 校验钱包充值是否存在 - PayWalletRechargeDO recharge = walletRechargeMapper.selectById(id); - if (recharge == null) { - log.error("[updateWalletRechargerPaid][recharge({}) payOrder({}) 不存在充值订单,请进行处理!]", id, payOrderId); - throw exception(WALLET_RECHARGE_NOT_FOUND); - } - // 1.2 校验钱包充值是否可以支付 - if (recharge.getPayStatus()) { - // 特殊:支付单号相同,直接返回,说明重复回调 - if (ObjectUtil.equals(recharge.getPayOrderId(), payOrderId)) { - log.warn("[updateWalletRechargerPaid][recharge({}) 已支付,且支付单号相同({}),直接返回]", recharge, payOrderId); - return; - } - // 异常:支付单号不同,说明支付单号错误 - log.error("[updateWalletRechargerPaid][recharge({}) 已支付,但是支付单号不同({}),请进行处理!]", recharge, payOrderId); - throw exception(WALLET_RECHARGE_UPDATE_PAID_PAY_ORDER_ID_ERROR); - } - - // 2. 校验支付订单的合法性 - PayOrderDO payOrderDO = validatePayOrderPaid(recharge, payOrderId); - - // 3. 更新钱包充值的支付状态 - int updateCount = walletRechargeMapper.updateByIdAndPaid(id, false, - new PayWalletRechargeDO().setId(id).setPayStatus(true).setPayTime(LocalDateTime.now()) - .setPayChannelCode(payOrderDO.getChannelCode())); - if (updateCount == 0) { - throw exception(WALLET_RECHARGE_UPDATE_PAID_STATUS_NOT_UNPAID); - } - - // 4. 更新钱包余额 - // TODO @jason:这样的话,未来提现会不会把充值的,也提现走哈。类似先充 100,送 110;然后提现 110; - // TODO 需要钱包中加个可提现余额 - payWalletService.addWalletBalance(recharge.getWalletId(), String.valueOf(id), - PayWalletBizTypeEnum.RECHARGE, recharge.getTotalPrice()); - - // 5. 发送订阅消息 - getSelf().sendWalletRechargerPaidMessage(payOrderId, recharge); - } - - @Async - public void sendWalletRechargerPaidMessage(Long payOrderId, PayWalletRechargeDO walletRecharge) { - // 1. 获得会员钱包信息 - PayWalletDO wallet = payWalletService.getWallet(walletRecharge.getWalletId()); - // 2. 构建并发送模版消息 - socialClientApi.sendWxaSubscribeMessage(new SocialWxaSubscribeMessageSendReqDTO() - .setUserId(wallet.getUserId()).setUserType(wallet.getUserType()) - .setTemplateTitle(WXA_WALLET_RECHARGER_PAID) - .setPage("pages/user/wallet/money") // 钱包详情界面 - .addMessage("character_string1", String.valueOf(payOrderId)) // 支付单编号 - .addMessage("amount2", fenToYuanStr(walletRecharge.getTotalPrice())) // 充值金额 - .addMessage("time3", LocalDateTimeUtil.formatNormal(walletRecharge.getCreateTime())) // 充值时间 - .addMessage("phrase4", "充值成功")); // 充值状态 - } - - @Override - @Transactional(rollbackFor = Exception.class) - public void refundWalletRecharge(Long id, String userIp) { - // 1.1 获取钱包充值记录 - PayWalletRechargeDO walletRecharge = walletRechargeMapper.selectById(id); - if (walletRecharge == null) { - log.error("[refundWalletRecharge][钱包充值记录不存在,钱包充值记录 id({})]", id); - throw exception(WALLET_RECHARGE_NOT_FOUND); - } - // 1.2 校验钱包充值是否可以发起退款 - PayWalletDO wallet = validateWalletRechargeCanRefund(walletRecharge); - - // 2. 冻结退款的余额,暂时只处理赠送的余额也全部退回 - payWalletService.freezePrice(wallet.getId(), walletRecharge.getTotalPrice()); - - // 3. 创建退款单 - String walletRechargeId = String.valueOf(id); - String refundId = walletRechargeId + "-refund"; - Long payRefundId = payRefundApi.createRefund(new PayRefundCreateReqDTO() - .setAppKey(payProperties.getWalletPayAppKey()).setUserIp(userIp) - .setMerchantOrderId(walletRechargeId) - .setMerchantRefundId(refundId) - .setReason("想退钱").setPrice(walletRecharge.getPayPrice())); - - // 4. 更新充值记录退款单号 - walletRechargeMapper.updateById(new PayWalletRechargeDO().setPayRefundId(payRefundId) - .setRefundStatus(WAITING.getStatus()).setId(walletRecharge.getId())); - } - - @Override - @Transactional(rollbackFor = Exception.class) - public void updateWalletRechargeRefunded(Long id, Long refundId, Long payRefundId) { - // 1.1 获取钱包充值记录 - // 说明:因为 id 和 refundId 是相同的,所以直接使用 id 查询即可! - PayWalletRechargeDO walletRecharge = walletRechargeMapper.selectById(id); - if (walletRecharge == null) { - log.error("[updateWalletRechargerPaid][钱包充值记录不存在,钱包充值记录 id({})]", id); - throw exception(WALLET_RECHARGE_NOT_FOUND); - } - // 1.2 校验钱包充值是否可以更新已退款 - PayRefundRespDTO payRefund = validateWalletRechargeCanRefunded(walletRecharge, payRefundId); - - // 2. 处理退款结果 - PayWalletRechargeDO updateObj = new PayWalletRechargeDO().setId(id); - // 情况一:退款成功 - if (PayRefundStatusEnum.isSuccess(payRefund.getStatus())) { - // 2.1 更新钱包余额 - payWalletService.reduceWalletBalance(walletRecharge.getWalletId(), id, - PayWalletBizTypeEnum.RECHARGE_REFUND, walletRecharge.getTotalPrice()); - - updateObj.setRefundStatus(SUCCESS.getStatus()).setRefundTime(payRefund.getSuccessTime()) - .setRefundTotalPrice(walletRecharge.getTotalPrice()).setRefundPayPrice(walletRecharge.getPayPrice()) - .setRefundBonusPrice(walletRecharge.getBonusPrice()); - // 情况二:退款失败 - } else if (PayRefundStatusEnum.isFailure(payRefund.getStatus())) { - // 2.2 解冻余额 - payWalletService.unfreezePrice(walletRecharge.getWalletId(), walletRecharge.getTotalPrice()); - - updateObj.setRefundStatus(FAILURE.getStatus()); - } - - // 3. 更新钱包充值的退款字段 - walletRechargeMapper.updateByIdAndRefunded(id, WAITING.getStatus(), updateObj); - } - - private PayRefundRespDTO validateWalletRechargeCanRefunded(PayWalletRechargeDO walletRecharge, Long payRefundId) { - // 1. 校验退款订单匹配 - if (notEqual(walletRecharge.getPayRefundId(), payRefundId)) { - log.error("[validateWalletRechargeCanRefunded][钱包充值({}) 退款单不匹配({}),请进行处理!钱包充值的数据是:{}]", - walletRecharge.getId(), payRefundId, toJsonString(walletRecharge)); - throw exception(WALLET_RECHARGE_REFUND_FAIL_REFUND_ORDER_ID_ERROR); - } - - // 2.1 校验退款订单 - PayRefundRespDTO payRefund = payRefundApi.getRefund(payRefundId); - if (payRefund == null) { - log.error("[validateWalletRechargeCanRefunded][payRefund({})不存在]", payRefundId); - throw exception(WALLET_RECHARGE_REFUND_FAIL_REFUND_NOT_FOUND); - } - // 2.2 校验退款金额一致 - if (notEqual(payRefund.getRefundPrice(), walletRecharge.getPayPrice())) { - log.error("[validateWalletRechargeCanRefunded][钱包({}) payRefund({}) 退款金额不匹配,请进行处理!钱包数据是:{},payRefund 数据是:{}]", - walletRecharge.getId(), payRefundId, toJsonString(walletRecharge), toJsonString(payRefund)); - throw exception(WALLET_RECHARGE_REFUND_FAIL_REFUND_PRICE_NOT_MATCH); - } - // 2.3 校验退款订单商户订单是否匹配 - if (notEqual(payRefund.getMerchantRefundId(), walletRecharge.getId().toString())) { - log.error("[validateWalletRechargeCanRefunded][钱包({}) 退款单不匹配({}),请进行处理!payRefund 数据是:{}]", - walletRecharge.getId(), payRefundId, toJsonString(payRefund)); - throw exception(WALLET_RECHARGE_REFUND_FAIL_REFUND_ORDER_ID_ERROR); - } - return payRefund; - } - - private PayWalletDO validateWalletRechargeCanRefund(PayWalletRechargeDO walletRecharge) { - // 校验充值订单是否支付 - if (!walletRecharge.getPayStatus()) { - throw exception(WALLET_RECHARGE_REFUND_FAIL_NOT_PAID); - } - // 校验充值订单是否已退款 - if (walletRecharge.getPayRefundId() != null) { - throw exception(WALLET_RECHARGE_REFUND_FAIL_REFUNDED); - } - // 校验钱包余额是否足够 - PayWalletDO wallet = payWalletService.getWallet(walletRecharge.getWalletId()); - Assert.notNull(wallet, "用户钱包({}) 不存在", wallet.getId()); - if (wallet.getBalance() < walletRecharge.getTotalPrice()) { - throw exception(WALLET_RECHARGE_REFUND_BALANCE_NOT_ENOUGH); - } - // TODO @芋艿:需要考虑下,赠送的金额,会不会导致提现超过; - return wallet; - } - - /** - * 校验支付订单的合法性 - * - * @param recharge 充值订单 - * @param payOrderId 支付订单编号 - * @return 支付订单 - */ - private PayOrderDO validatePayOrderPaid(PayWalletRechargeDO recharge, Long payOrderId) { - // 1. 校验支付单是否存在 - PayOrderDO payOrder = payOrderService.getOrder(payOrderId); - if (payOrder == null) { - log.error("[validatePayOrderPaid][充值订单({}) payOrder({}) 不存在,请进行处理!]", - recharge.getId(), payOrderId); - throw exception(PAY_ORDER_NOT_FOUND); - } - - // 2.1 校验支付单已支付 - if (!PayOrderStatusEnum.isSuccess(payOrder.getStatus())) { - log.error("[validatePayOrderPaid][充值订单({}) payOrder({}) 未支付,请进行处理!payOrder 数据是:{}]", - recharge.getId(), payOrderId, toJsonString(payOrder)); - throw exception(WALLET_RECHARGE_UPDATE_PAID_PAY_ORDER_STATUS_NOT_SUCCESS); - } - // 2.2 校验支付金额一致 - if (notEqual(payOrder.getPrice(), recharge.getPayPrice())) { - log.error("[validatePayOrderPaid][充值订单({}) payOrder({}) 支付金额不匹配,请进行处理!钱包 数据是:{},payOrder 数据是:{}]", - recharge.getId(), payOrderId, toJsonString(recharge), toJsonString(payOrder)); - throw exception(WALLET_RECHARGE_UPDATE_PAID_PAY_PRICE_NOT_MATCH); - } - // 2.3 校验支付订单的商户订单匹配 - if (notEqual(payOrder.getMerchantOrderId(), recharge.getId().toString())) { - log.error("[validatePayOrderPaid][充值订单({}) 支付单不匹配({}),请进行处理!payOrder 数据是:{}]", - recharge.getId(), payOrderId, toJsonString(payOrder)); - throw exception(WALLET_RECHARGE_UPDATE_PAID_PAY_ORDER_ID_ERROR); - } - return payOrder; - } - - /** - * 获得自身的代理对象,解决 AOP 生效问题 - * - * @return 自己 - */ - private PayWalletRechargeServiceImpl getSelf() { - return SpringUtil.getBean(getClass()); - } - -} diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/wallet/PayWalletService.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/wallet/PayWalletService.java deleted file mode 100644 index 6efdc92..0000000 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/wallet/PayWalletService.java +++ /dev/null @@ -1,99 +0,0 @@ -package cn.iocoder.yudao.module.pay.service.wallet; - -import cn.iocoder.yudao.framework.common.pojo.PageResult; -import cn.iocoder.yudao.module.pay.controller.admin.wallet.vo.wallet.PayWalletPageReqVO; -import cn.iocoder.yudao.module.pay.dal.dataobject.wallet.PayWalletDO; -import cn.iocoder.yudao.module.pay.dal.dataobject.wallet.PayWalletTransactionDO; -import cn.iocoder.yudao.module.pay.enums.wallet.PayWalletBizTypeEnum; - -/** - * 钱包 Service 接口 - * - * @author jason - */ -public interface PayWalletService { - - /** - * 获取钱包信息 - *

- * 如果不存在,则创建钱包。由于用户注册时候不会创建钱包 - * - * @param userId 用户编号 - * @param userType 用户类型 - */ - PayWalletDO getOrCreateWallet(Long userId, Integer userType); - - /** - * 获取钱包信息 - * - * @param walletId 钱包 id - */ - PayWalletDO getWallet(Long walletId); - - /** - * 获得会员钱包分页 - * - * @param pageReqVO 分页查询 - * @return 会员钱包分页 - */ - PageResult getWalletPage(PayWalletPageReqVO pageReqVO); - - /** - * 钱包订单支付 - * - * @param walletId 钱包编号 - * @param outTradeNo 外部订单号 - * @param price 金额 - */ - PayWalletTransactionDO orderPay(Long walletId, String outTradeNo, Integer price); - - /** - * 钱包订单支付退款 - * - * @param outRefundNo 外部退款号 - * @param refundPrice 退款金额 - * @param reason 退款原因 - */ - PayWalletTransactionDO orderRefund(String outRefundNo, Integer refundPrice, String reason); - - /** - * 扣减钱包余额 - * - * @param walletId 钱包编号 - * @param bizId 业务关联编号 - * @param bizType 业务关联分类 - * @param price 扣减金额 - * @return 钱包流水 - */ - PayWalletTransactionDO reduceWalletBalance(Long walletId, Long bizId, - PayWalletBizTypeEnum bizType, Integer price); - - /** - * 增加钱包余额 - * - * @param walletId 钱包编号 - * @param bizId 业务关联编号 - * @param bizType 业务关联分类 - * @param price 增加金额 - * @return 钱包流水 - */ - PayWalletTransactionDO addWalletBalance(Long walletId, String bizId, - PayWalletBizTypeEnum bizType, Integer price); - - /** - * 冻结钱包部分余额 - * - * @param id 钱包编号 - * @param price 冻结金额 - */ - void freezePrice(Long id, Integer price); - - /** - * 解冻钱包余额 - * - * @param id 钱包编号 - * @param price 解冻金额 - */ - void unfreezePrice(Long id, Integer price); - -} diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/wallet/PayWalletServiceImpl.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/wallet/PayWalletServiceImpl.java deleted file mode 100644 index 0005ace..0000000 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/wallet/PayWalletServiceImpl.java +++ /dev/null @@ -1,233 +0,0 @@ -package cn.iocoder.yudao.module.pay.service.wallet; - -import cn.hutool.core.lang.Assert; -import cn.iocoder.yudao.framework.common.pojo.PageResult; -import cn.iocoder.yudao.framework.common.util.date.DateUtils; -import cn.iocoder.yudao.module.pay.controller.admin.wallet.vo.wallet.PayWalletPageReqVO; -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.wallet.PayWalletDO; -import cn.iocoder.yudao.module.pay.dal.dataobject.wallet.PayWalletTransactionDO; -import cn.iocoder.yudao.module.pay.dal.mysql.wallet.PayWalletMapper; -import cn.iocoder.yudao.module.pay.dal.redis.wallet.PayWalletLockRedisDAO; -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.wallet.bo.WalletTransactionCreateReqBO; -import jakarta.annotation.Resource; -import lombok.SneakyThrows; -import lombok.extern.slf4j.Slf4j; -import org.springframework.context.annotation.Lazy; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - -import java.time.LocalDateTime; - -import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; -import static cn.iocoder.yudao.module.pay.enums.ErrorCodeConstants.*; -import static cn.iocoder.yudao.module.pay.enums.wallet.PayWalletBizTypeEnum.PAYMENT; -import static cn.iocoder.yudao.module.pay.enums.wallet.PayWalletBizTypeEnum.PAYMENT_REFUND; - -/** - * 钱包 Service 实现类 - * - * @author jason - */ -@Service -@Slf4j -public class PayWalletServiceImpl implements PayWalletService { - - /** - * 通知超时时间,单位:毫秒 - */ - public static final long UPDATE_TIMEOUT_MILLIS = 120 * DateUtils.SECOND_MILLIS; - - @Resource - private PayWalletMapper walletMapper; - @Resource - private PayWalletLockRedisDAO lockRedisDAO; - - @Resource - @Lazy // 延迟加载,避免循环依赖 - private PayWalletTransactionService walletTransactionService; - @Resource - @Lazy // 延迟加载,避免循环依赖 - private PayOrderService orderService; - @Resource - @Lazy // 延迟加载,避免循环依赖 - private PayRefundService refundService; - - @Override - public PayWalletDO getOrCreateWallet(Long userId, Integer userType) { - PayWalletDO wallet = walletMapper.selectByUserIdAndType(userId, userType); - if (wallet == null) { - wallet = new PayWalletDO().setUserId(userId).setUserType(userType) - .setBalance(0).setTotalExpense(0).setTotalRecharge(0); - wallet.setCreateTime(LocalDateTime.now()); - walletMapper.insert(wallet); - } - return wallet; - } - - @Override - public PayWalletDO getWallet(Long walletId) { - return walletMapper.selectById(walletId); - } - - @Override - public PageResult getWalletPage(PayWalletPageReqVO pageReqVO) { - return walletMapper.selectPage(pageReqVO); - } - - @Override - @Transactional(rollbackFor = Exception.class) - public PayWalletTransactionDO orderPay(Long walletId, String outTradeNo, Integer price) { - // 1. 判断支付交易拓展单是否存 - PayOrderExtensionDO orderExtension = orderService.getOrderExtensionByNo(outTradeNo); - if (orderExtension == null) { - throw exception(PAY_ORDER_EXTENSION_NOT_FOUND); - } - PayWalletDO wallet = walletMapper.selectById(walletId); - // 2. 扣减余额 - return reduceWalletBalance(wallet.getId(), orderExtension.getOrderId(), PAYMENT, price); - } - - @Override - @Transactional(rollbackFor = Exception.class) - public PayWalletTransactionDO orderRefund(String outRefundNo, Integer refundPrice, String reason) { - // 1.1 判断退款单是否存在 - PayRefundDO payRefund = refundService.getRefundByNo(outRefundNo); - if (payRefund == null) { - throw exception(REFUND_NOT_FOUND); - } - // 1.2 校验是否可以退款 - Long walletId = validateWalletCanRefund(payRefund.getId(), payRefund.getChannelOrderNo()); - PayWalletDO wallet = walletMapper.selectById(walletId); - Assert.notNull(wallet, "钱包 {} 不存在", walletId); - - // 2. 增加余额 - return addWalletBalance(walletId, String.valueOf(payRefund.getId()), PAYMENT_REFUND, refundPrice); - } - - /** - * 校验是否能退款 - * - * @param refundId 支付退款单 id - * @param walletPayNo 钱包支付 no - */ - private Long validateWalletCanRefund(Long refundId, String walletPayNo) { - // 1. 校验钱包支付交易存在 - PayWalletTransactionDO walletTransaction = walletTransactionService.getWalletTransactionByNo(walletPayNo); - if (walletTransaction == null) { - throw exception(WALLET_TRANSACTION_NOT_FOUND); - } - // 2. 校验退款是否存在 - PayWalletTransactionDO refundTransaction = walletTransactionService.getWalletTransaction( - String.valueOf(refundId), PAYMENT_REFUND); - if (refundTransaction != null) { - throw exception(WALLET_REFUND_EXIST); - } - return walletTransaction.getWalletId(); - } - - @Override - @Transactional(rollbackFor = Exception.class) - @SneakyThrows - public PayWalletTransactionDO reduceWalletBalance(Long walletId, Long bizId, - PayWalletBizTypeEnum bizType, Integer price) { - // 1. 获取钱包 - PayWalletDO payWallet = getWallet(walletId); - if (payWallet == null) { - log.error("[reduceWalletBalance][用户钱包({})不存在]", walletId); - throw exception(WALLET_NOT_FOUND); - } - - // 2. 加锁,更新钱包余额(目的:避免钱包流水的并发更新时,余额变化不连贯) - return lockRedisDAO.lock(walletId, UPDATE_TIMEOUT_MILLIS, () -> { - // 2. 扣除余额 - int updateCounts; - switch (bizType) { - case PAYMENT: { - updateCounts = walletMapper.updateWhenConsumption(payWallet.getId(), price); - break; - } - case RECHARGE_REFUND: { - updateCounts = walletMapper.updateWhenRechargeRefund(payWallet.getId(), price); - break; - } - default: { - // TODO 其它类型待实现 - throw new UnsupportedOperationException("待实现"); - } - } - if (updateCounts == 0) { - throw exception(WALLET_BALANCE_NOT_ENOUGH); - } - - // 3. 生成钱包流水 - Integer afterBalance = payWallet.getBalance() - price; - WalletTransactionCreateReqBO bo = new WalletTransactionCreateReqBO().setWalletId(payWallet.getId()) - .setPrice(-price).setBalance(afterBalance).setBizId(String.valueOf(bizId)) - .setBizType(bizType.getType()).setTitle(bizType.getDescription()); - return walletTransactionService.createWalletTransaction(bo); - }); - } - - @Override - @Transactional(rollbackFor = Exception.class) - @SneakyThrows - public PayWalletTransactionDO addWalletBalance(Long walletId, String bizId, - PayWalletBizTypeEnum bizType, Integer price) { - // 1. 获取钱包 - PayWalletDO payWallet = getWallet(walletId); - if (payWallet == null) { - log.error("[addWalletBalance][用户钱包({})不存在]", walletId); - throw exception(WALLET_NOT_FOUND); - } - - // 2. 加锁,更新钱包余额(目的:避免钱包流水的并发更新时,余额变化不连贯) - return lockRedisDAO.lock(walletId, UPDATE_TIMEOUT_MILLIS, () -> { - // 3. 更新钱包金额 - switch (bizType) { - case PAYMENT_REFUND: { // 退款更新 - walletMapper.updateWhenConsumptionRefund(payWallet.getId(), price); - break; - } - case RECHARGE: { // 充值更新 - walletMapper.updateWhenRecharge(payWallet.getId(), price); - break; - } - case UPDATE_BALANCE: // 更新余额 - case TRANSFER: // 分佣提现 - walletMapper.updateWhenAdd(payWallet.getId(), price); - break; - default: { - throw new UnsupportedOperationException("待实现:" + bizType); - } - } - - // 4. 生成钱包流水 - WalletTransactionCreateReqBO transactionCreateReqBO = new WalletTransactionCreateReqBO() - .setWalletId(payWallet.getId()).setPrice(price).setBalance(payWallet.getBalance() + price) - .setBizId(bizId).setBizType(bizType.getType()).setTitle(bizType.getDescription()); - return walletTransactionService.createWalletTransaction(transactionCreateReqBO); - }); - } - - @Override - public void freezePrice(Long id, Integer price) { - int updateCounts = walletMapper.freezePrice(id, price); - if (updateCounts == 0) { - throw exception(WALLET_BALANCE_NOT_ENOUGH); - } - } - - @Override - public void unfreezePrice(Long id, Integer price) { - int updateCounts = walletMapper.unFreezePrice(id, price); - if (updateCounts == 0) { - throw exception(WALLET_FREEZE_PRICE_NOT_ENOUGH); - } - } - -} diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/wallet/PayWalletTransactionService.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/wallet/PayWalletTransactionService.java deleted file mode 100644 index af9c6a8..0000000 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/wallet/PayWalletTransactionService.java +++ /dev/null @@ -1,74 +0,0 @@ -package cn.iocoder.yudao.module.pay.service.wallet; - -import cn.iocoder.yudao.framework.common.pojo.PageResult; -import cn.iocoder.yudao.module.pay.controller.admin.wallet.vo.transaction.PayWalletTransactionPageReqVO; -import cn.iocoder.yudao.module.pay.controller.app.wallet.vo.transaction.AppPayWalletTransactionPageReqVO; -import cn.iocoder.yudao.module.pay.controller.app.wallet.vo.transaction.AppPayWalletTransactionSummaryRespVO; -import cn.iocoder.yudao.module.pay.dal.dataobject.wallet.PayWalletTransactionDO; -import cn.iocoder.yudao.module.pay.enums.wallet.PayWalletBizTypeEnum; -import cn.iocoder.yudao.module.pay.service.wallet.bo.WalletTransactionCreateReqBO; - -import jakarta.validation.Valid; - -import java.time.LocalDateTime; - -/** - * 钱包余额流水 Service 接口 - * - * @author jason - */ -public interface PayWalletTransactionService { - - /** - * 查询钱包余额流水分页 - * - * @param userId 用户编号 - * @param userType 用户类型 - * @param pageVO 分页查询参数 - */ - PageResult getWalletTransactionPage(Long userId, Integer userType, - AppPayWalletTransactionPageReqVO pageVO); - - /** - * 查询钱包余额流水分页 - * - * @param pageVO 分页查询参数 - */ - PageResult getWalletTransactionPage(PayWalletTransactionPageReqVO pageVO); - - /** - * 新增钱包余额流水 - * - * @param bo 创建钱包流水 bo - * @return 新建的钱包 do - */ - PayWalletTransactionDO createWalletTransaction(@Valid WalletTransactionCreateReqBO bo); - - /** - * 根据 no,获取钱包余流水 - * - * @param no 流水号 - */ - PayWalletTransactionDO getWalletTransactionByNo(String no); - - /** - * 获取钱包流水 - * - * @param bizId 业务编号 - * @param type 业务类型 - * @return 钱包流水 - */ - PayWalletTransactionDO getWalletTransaction(String bizId, PayWalletBizTypeEnum type); - - /** - * 获得钱包流水统计 - * - * @param userId 用户编号 - * @param userType 用户类型 - * @param createTime 时间段 - * @return 钱包流水统计 - */ - AppPayWalletTransactionSummaryRespVO getWalletTransactionSummary(Long userId, Integer userType, - LocalDateTime[] createTime); - -} diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/wallet/PayWalletTransactionServiceImpl.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/wallet/PayWalletTransactionServiceImpl.java deleted file mode 100644 index 4c01cd8..0000000 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/wallet/PayWalletTransactionServiceImpl.java +++ /dev/null @@ -1,95 +0,0 @@ -package cn.iocoder.yudao.module.pay.service.wallet; - -import cn.hutool.core.util.ObjectUtil; -import cn.iocoder.yudao.framework.common.pojo.PageResult; -import cn.iocoder.yudao.module.pay.controller.admin.wallet.vo.transaction.PayWalletTransactionPageReqVO; -import cn.iocoder.yudao.module.pay.controller.app.wallet.vo.transaction.AppPayWalletTransactionPageReqVO; -import cn.iocoder.yudao.module.pay.controller.app.wallet.vo.transaction.AppPayWalletTransactionSummaryRespVO; -import cn.iocoder.yudao.module.pay.convert.wallet.PayWalletTransactionConvert; -import cn.iocoder.yudao.module.pay.dal.dataobject.wallet.PayWalletDO; -import cn.iocoder.yudao.module.pay.dal.dataobject.wallet.PayWalletTransactionDO; -import cn.iocoder.yudao.module.pay.dal.mysql.wallet.PayWalletTransactionMapper; -import cn.iocoder.yudao.module.pay.dal.redis.no.PayNoRedisDAO; -import cn.iocoder.yudao.module.pay.enums.wallet.PayWalletBizTypeEnum; -import cn.iocoder.yudao.module.pay.service.wallet.bo.WalletTransactionCreateReqBO; -import jakarta.annotation.Resource; -import lombok.extern.slf4j.Slf4j; -import org.springframework.stereotype.Service; -import org.springframework.validation.annotation.Validated; - -import java.time.LocalDateTime; - -import static cn.iocoder.yudao.module.pay.controller.app.wallet.vo.transaction.AppPayWalletTransactionPageReqVO.TYPE_EXPENSE; -import static cn.iocoder.yudao.module.pay.controller.app.wallet.vo.transaction.AppPayWalletTransactionPageReqVO.TYPE_INCOME; - -/** - * 钱包流水 Service 实现类 - * - * @author jason - */ -@Service -@Slf4j -@Validated -public class PayWalletTransactionServiceImpl implements PayWalletTransactionService { - - /** - * 钱包流水的 no 前缀 - */ - private static final String WALLET_NO_PREFIX = "W"; - - @Resource - private PayWalletService payWalletService; - @Resource - private PayWalletTransactionMapper payWalletTransactionMapper; - @Resource - private PayNoRedisDAO noRedisDAO; - - @Override - public PageResult getWalletTransactionPage(Long userId, Integer userType, - AppPayWalletTransactionPageReqVO pageVO) { - PayWalletDO wallet = payWalletService.getOrCreateWallet(userId, userType); - return payWalletTransactionMapper.selectPage(wallet.getId(), pageVO.getType(), pageVO, pageVO.getCreateTime()); - } - - @Override - public PageResult getWalletTransactionPage(PayWalletTransactionPageReqVO pageVO) { - // 基于 userId + userType 查询钱包 - if (pageVO.getWalletId() == null - && ObjectUtil.isAllNotEmpty(pageVO.getUserId(), pageVO.getUserType())) { - PayWalletDO wallet = payWalletService.getOrCreateWallet(pageVO.getUserId(), pageVO.getUserType()); - if (wallet != null) { - pageVO.setWalletId(wallet.getId()); - } - } - - // 查询分页 - return payWalletTransactionMapper.selectPage(pageVO.getWalletId(), null, pageVO, null); - } - - @Override - public PayWalletTransactionDO createWalletTransaction(WalletTransactionCreateReqBO bo) { - PayWalletTransactionDO transaction = PayWalletTransactionConvert.INSTANCE.convert(bo) - .setNo(noRedisDAO.generate(WALLET_NO_PREFIX)); - payWalletTransactionMapper.insert(transaction); - return transaction; - } - - @Override - public PayWalletTransactionDO getWalletTransactionByNo(String no) { - return payWalletTransactionMapper.selectByNo(no); - } - - @Override - public PayWalletTransactionDO getWalletTransaction(String bizId, PayWalletBizTypeEnum type) { - return payWalletTransactionMapper.selectByBiz(bizId, type.getType()); - } - - @Override - public AppPayWalletTransactionSummaryRespVO getWalletTransactionSummary(Long userId, Integer userType, LocalDateTime[] createTime) { - PayWalletDO wallet = payWalletService.getOrCreateWallet(userId, userType); - return new AppPayWalletTransactionSummaryRespVO() - .setTotalExpense(payWalletTransactionMapper.selectPriceSum(wallet.getId(), TYPE_EXPENSE, createTime)) - .setTotalIncome(payWalletTransactionMapper.selectPriceSum(wallet.getId(), TYPE_INCOME, createTime)); - } - -} diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/wallet/bo/WalletTransactionCreateReqBO.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/wallet/bo/WalletTransactionCreateReqBO.java deleted file mode 100644 index cde57f1..0000000 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/wallet/bo/WalletTransactionCreateReqBO.java +++ /dev/null @@ -1,59 +0,0 @@ -package cn.iocoder.yudao.module.pay.service.wallet.bo; - -import cn.iocoder.yudao.framework.common.validation.InEnum; -import cn.iocoder.yudao.module.pay.enums.wallet.PayWalletBizTypeEnum; -import lombok.Data; - -import jakarta.validation.constraints.NotEmpty; -import jakarta.validation.constraints.NotNull; - -/** - * 创建钱包流水 BO - * - * @author jason - */ -@Data -public class WalletTransactionCreateReqBO { - - /** - * 钱包编号 - * - */ - @NotNull(message = "钱包编号不能为空") - private Long walletId; - - /** - * 交易金额,单位分 - * - * 正值表示余额增加,负值表示余额减少 - */ - @NotNull(message = "交易金额不能为空") - private Integer price; - - /** - * 交易后余额,单位分 - */ - @NotNull(message = "交易后余额不能为空") - private Integer balance; - - /** - * 关联业务分类 - * - * 枚举 {@link PayWalletBizTypeEnum#getType()} - */ - @NotNull(message = "关联业务分类不能为空") - @InEnum(PayWalletBizTypeEnum.class) - private Integer bizType; - - /** - * 关联业务编号 - */ - @NotEmpty(message = "关联业务编号不能为空") - private String bizId; - - /** - * 流水说明 - */ - @NotEmpty(message = "流水说明不能为空") - private String title; -} 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 deleted file mode 100644 index 20a4b70..0000000 --- a/yudao-module-pay/yudao-module-pay-biz/src/test/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/impl/PayClientFactoryImplIntegrationTest.java +++ /dev/null @@ -1,134 +0,0 @@ -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 deleted file mode 100644 index cb723c9..0000000 --- a/yudao-module-pay/yudao-module-pay-biz/src/test/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/impl/alipay/AbstractAlipayClientTest.java +++ /dev/null @@ -1,221 +0,0 @@ -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 deleted file mode 100644 index 3e6dda1..0000000 --- a/yudao-module-pay/yudao-module-pay-biz/src/test/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/impl/alipay/AlipayBarPayClientTest.java +++ /dev/null @@ -1,170 +0,0 @@ -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 deleted file mode 100644 index 62041f9..0000000 --- a/yudao-module-pay/yudao-module-pay-biz/src/test/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/impl/alipay/AlipayPcPayClientTest.java +++ /dev/null @@ -1,131 +0,0 @@ -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 deleted file mode 100644 index c82a004..0000000 --- a/yudao-module-pay/yudao-module-pay-biz/src/test/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/impl/alipay/AlipayQrPayClientTest.java +++ /dev/null @@ -1,147 +0,0 @@ -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 deleted file mode 100644 index a094b68..0000000 --- a/yudao-module-pay/yudao-module-pay-biz/src/test/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/impl/alipay/AlipayWapPayClientTest.java +++ /dev/null @@ -1,111 +0,0 @@ -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 deleted file mode 100644 index 9e79bb3..0000000 --- a/yudao-module-pay/yudao-module-pay-biz/src/test/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/impl/weixin/WxBarPayClientIntegrationTest.java +++ /dev/null @@ -1,123 +0,0 @@ -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 deleted file mode 100644 index 4dd13eb..0000000 --- a/yudao-module-pay/yudao-module-pay-biz/src/test/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/impl/weixin/WxNativePayClientIntegrationTest.java +++ /dev/null @@ -1,84 +0,0 @@ -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/app/PayAppServiceTest.java b/yudao-module-pay/yudao-module-pay-biz/src/test/java/cn/iocoder/yudao/module/pay/service/app/PayAppServiceTest.java deleted file mode 100644 index 326d586..0000000 --- a/yudao-module-pay/yudao-module-pay-biz/src/test/java/cn/iocoder/yudao/module/pay/service/app/PayAppServiceTest.java +++ /dev/null @@ -1,260 +0,0 @@ -package cn.iocoder.yudao.module.pay.service.app; - -import cn.hutool.core.util.RandomUtil; -import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; -import cn.iocoder.yudao.framework.common.pojo.PageResult; -import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest; -import cn.iocoder.yudao.module.pay.controller.admin.app.vo.PayAppCreateReqVO; -import cn.iocoder.yudao.module.pay.controller.admin.app.vo.PayAppPageReqVO; -import cn.iocoder.yudao.module.pay.controller.admin.app.vo.PayAppUpdateReqVO; -import cn.iocoder.yudao.module.pay.dal.dataobject.app.PayAppDO; -import cn.iocoder.yudao.module.pay.dal.mysql.app.PayAppMapper; -import cn.iocoder.yudao.module.pay.service.order.PayOrderService; -import cn.iocoder.yudao.module.pay.service.refund.PayRefundService; -import org.junit.jupiter.api.Disabled; -import org.junit.jupiter.api.Test; -import org.springframework.boot.test.mock.mockito.MockBean; -import org.springframework.context.annotation.Import; - -import jakarta.annotation.Resource; -import java.util.Map; - -import static cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.buildBetweenTime; -import static cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.buildTime; -import static cn.iocoder.yudao.framework.common.util.object.ObjectUtils.cloneIgnoreId; -import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertPojoEquals; -import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertServiceException; -import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.*; -import static cn.iocoder.yudao.module.pay.enums.ErrorCodeConstants.*; -import static java.util.Collections.singleton; -import static org.junit.jupiter.api.Assertions.*; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.when; - -/** - * {@link PayAppServiceImpl} 的单元测试 - * - * @author aquan - */ -@Disabled // TODO 芋艿:后续 fix 补充的单测 -@Import(PayAppServiceImpl.class) -public class PayAppServiceTest extends BaseDbUnitTest { - - @Resource - private PayAppServiceImpl appService; - - @Resource - private PayAppMapper appMapper; - - @MockBean - private PayOrderService orderService; - @MockBean - private PayRefundService refundService; - - @Test - public void testCreateApp_success() { - // 准备参数 - PayAppCreateReqVO reqVO = randomPojo(PayAppCreateReqVO.class, o -> - o.setStatus((RandomUtil.randomEle(CommonStatusEnum.values()).getStatus())) - .setOrderNotifyUrl(randomURL()) - .setRefundNotifyUrl(randomURL())); - - // 调用 - Long appId = appService.createApp(reqVO); - // 断言 - assertNotNull(appId); - PayAppDO app = appMapper.selectById(appId); - assertPojoEquals(reqVO, app); - } - - @Test - public void testUpdateApp_success() { - // mock 数据 - PayAppDO dbApp = randomPojo(PayAppDO.class); - appMapper.insert(dbApp);// @Sql: 先插入出一条存在的数据 - // 准备参数 - PayAppUpdateReqVO reqVO = randomPojo(PayAppUpdateReqVO.class, o -> { - o.setStatus(CommonStatusEnum.ENABLE.getStatus()); - o.setOrderNotifyUrl(randomURL()).setRefundNotifyUrl(randomURL()); - o.setId(dbApp.getId()); // 设置更新的 ID - }); - - // 调用 - appService.updateApp(reqVO); - // 校验是否更新正确 - PayAppDO app = appMapper.selectById(reqVO.getId()); // 获取最新的 - assertPojoEquals(reqVO, app); - } - - @Test - public void testUpdateApp_notExists() { - // 准备参数 - PayAppUpdateReqVO reqVO = randomPojo(PayAppUpdateReqVO.class, o -> - o.setStatus((RandomUtil.randomEle(CommonStatusEnum.values()).getStatus()))); - // 调用, 并断言异常 - assertServiceException(() -> appService.updateApp(reqVO), APP_NOT_FOUND); - } - - @Test - public void testUpdateAppStatus() { - // mock 数据 - PayAppDO dbApp = randomPojo(PayAppDO.class, o -> - o.setStatus(CommonStatusEnum.DISABLE.getStatus())); - appMapper.insert(dbApp);// @Sql: 先插入出一条存在的数据 - - // 准备参数 - Long id = dbApp.getId(); - Integer status = CommonStatusEnum.ENABLE.getStatus(); - // 调用 - appService.updateAppStatus(id, status); - // 断言 - PayAppDO app = appMapper.selectById(id); // 获取最新的 - assertEquals(status, app.getStatus()); - } - - @Test - public void testDeleteApp_success() { - // mock 数据 - PayAppDO dbApp = randomPojo(PayAppDO.class); - appMapper.insert(dbApp);// @Sql: 先插入出一条存在的数据 - // 准备参数 - Long id = dbApp.getId(); - - // 调用 - appService.deleteApp(id); - // 校验数据不存在了 - assertNull(appMapper.selectById(id)); - } - - @Test - public void testDeleteApp_notExists() { - // 准备参数 - Long id = randomLongId(); - - // 调用, 并断言异常 - assertServiceException(() -> appService.deleteApp(id), APP_NOT_FOUND); - } - - @Test - public void testDeleteApp_existOrder() { - // mock 数据 - PayAppDO dbApp = randomPojo(PayAppDO.class); - appMapper.insert(dbApp);// @Sql: 先插入出一条存在的数据 - // 准备参数 - Long id = dbApp.getId(); - // mock 订单有订单 - when(orderService.getOrderCountByAppId(eq(id))).thenReturn(10L); - - // 调用, 并断言异常 - assertServiceException(() -> appService.deleteApp(id), APP_EXIST_ORDER_CANT_DELETE); - } - - @Test - public void testDeleteApp_existRefund() { - // mock 数据 - PayAppDO dbApp = randomPojo(PayAppDO.class); - appMapper.insert(dbApp);// @Sql: 先插入出一条存在的数据 - // 准备参数 - Long id = dbApp.getId(); - // mock 订单有订单 - when(refundService.getRefundCountByAppId(eq(id))).thenReturn(10L); - - // 调用, 并断言异常 - assertServiceException(() -> appService.deleteApp(id), APP_EXIST_REFUND_CANT_DELETE); - } - - @Test - public void testApp() { - // mock 数据 - PayAppDO dbApp = randomPojo(PayAppDO.class); - appMapper.insert(dbApp);// @Sql: 先插入出一条存在的数据 - // 准备参数 - Long id = dbApp.getId(); - - // 调用 - PayAppDO app = appService.getApp(id); - // 校验数据一致 - assertPojoEquals(app, dbApp); - } - - @Test - public void testAppMap() { - // mock 数据 - PayAppDO dbApp01 = randomPojo(PayAppDO.class); - appMapper.insert(dbApp01);// @Sql: 先插入出一条存在的数据 - PayAppDO dbApp02 = randomPojo(PayAppDO.class); - appMapper.insert(dbApp02);// @Sql: 先插入出一条存在的数据 - // 准备参数 - Long id = dbApp01.getId(); - - // 调用 - Map appMap = appService.getAppMap(singleton(id)); - // 校验数据一致 - assertEquals(1, appMap.size()); - assertPojoEquals(dbApp01, appMap.get(id)); - } - - @Test - public void testGetAppPage() { - // mock 数据 - PayAppDO dbApp = randomPojo(PayAppDO.class, o -> { // 等会查询到 - o.setName("灿灿姐的杂货铺"); - o.setStatus(CommonStatusEnum.ENABLE.getStatus()); - o.setCreateTime(buildTime(2021,11,20)); - }); - - appMapper.insert(dbApp); - // 测试 name 不匹配 - appMapper.insert(cloneIgnoreId(dbApp, o -> o.setName("敏敏姐的杂货铺"))); - // 测试 status 不匹配 - appMapper.insert(cloneIgnoreId(dbApp, o -> o.setStatus(CommonStatusEnum.DISABLE.getStatus()))); - // 测试 createTime 不匹配 - appMapper.insert(cloneIgnoreId(dbApp, o -> o.setCreateTime(buildTime(2021,12,21)))); - // 准备参数 - PayAppPageReqVO reqVO = new PayAppPageReqVO(); - reqVO.setName("灿灿姐的杂货铺"); - reqVO.setStatus(CommonStatusEnum.ENABLE.getStatus()); - reqVO.setCreateTime(buildBetweenTime(2021, 11, 19, 2021, 11, 21)); - - // 调用 - PageResult pageResult = appService.getAppPage(reqVO); - // 断言 - assertEquals(1, pageResult.getTotal()); - assertEquals(1, pageResult.getList().size()); - assertPojoEquals(dbApp, pageResult.getList().get(0)); - } - - @Test - public void testValidPayApp_success() { - // mock 数据 - PayAppDO dbApp = randomPojo(PayAppDO.class, - o -> o.setStatus(CommonStatusEnum.ENABLE.getStatus())); - appMapper.insert(dbApp);// @Sql: 先插入出一条存在的数据 - // 准备参数 - Long id = dbApp.getId(); - - // 调用 - PayAppDO app = appService.validPayApp(id); - // 校验数据一致 - assertPojoEquals(app, dbApp); - } - - @Test - public void testValidPayApp_notFound() { - assertServiceException(() -> appService.validPayApp(randomLongId()), APP_NOT_FOUND); - } - - @Test - public void testValidPayApp_disable() { - // mock 数据 - PayAppDO dbApp = randomPojo(PayAppDO.class, - o -> o.setStatus(CommonStatusEnum.DISABLE.getStatus())); - appMapper.insert(dbApp);// @Sql: 先插入出一条存在的数据 - // 准备参数 - Long id = dbApp.getId(); - - // 调用,并断言异常 - assertServiceException(() -> appService.validPayApp(id), APP_IS_DISABLE); - } - -} 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 deleted file mode 100644 index 4e05b87..0000000 --- a/yudao-module-pay/yudao-module-pay-biz/src/test/java/cn/iocoder/yudao/module/pay/service/channel/PayChannelServiceTest.java +++ /dev/null @@ -1,337 +0,0 @@ -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.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; -import cn.iocoder.yudao.module.pay.dal.dataobject.channel.PayChannelDO; -import cn.iocoder.yudao.module.pay.dal.mysql.channel.PayChannelMapper; -import com.alibaba.fastjson.JSON; -import org.junit.jupiter.api.Test; -import org.springframework.boot.test.mock.mockito.MockBean; -import org.springframework.context.annotation.Import; - -import jakarta.annotation.Resource; -import jakarta.validation.Validator; -import java.util.Collections; -import java.util.List; - -import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertPojoEquals; -import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertServiceException; -import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.*; -import static cn.iocoder.yudao.module.pay.enums.ErrorCodeConstants.*; -import static org.junit.jupiter.api.Assertions.*; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.*; - -@Import({PayChannelServiceImpl.class}) -public class PayChannelServiceTest extends BaseDbUnitTest { - - @Resource - private PayChannelServiceImpl channelService; - - @Resource - private PayChannelMapper channelMapper; - - @MockBean - private PayClientFactory payClientFactory; - @MockBean - private Validator validator; - - @Test - public void testCreateChannel_success() { - // 准备参数 - WxPayClientConfig config = randomWxPayClientConfig(); - PayChannelCreateReqVO reqVO = randomPojo(PayChannelCreateReqVO.class, o -> { - o.setStatus(randomCommonStatus()); - o.setCode(PayChannelEnum.WX_PUB.getCode()); - o.setConfig(JsonUtils.toJsonString(config)); - }); - - // 调用 - Long channelId = channelService.createChannel(reqVO); - // 校验记录的属性是否正确 - PayChannelDO channel = channelMapper.selectById(channelId); - assertPojoEquals(reqVO, channel, "config"); - assertPojoEquals(config, channel.getConfig()); - } - - @Test - public void testCreateChannel_exists() { - // mock 数据 - PayChannelDO dbChannel = randomPojo(PayChannelDO.class, - o -> o.setConfig(randomWxPayClientConfig())); - channelMapper.insert(dbChannel);// @Sql: 先插入出一条存在的数据 - // 准备参数 - PayChannelCreateReqVO reqVO = randomPojo(PayChannelCreateReqVO.class, o -> { - o.setAppId(dbChannel.getAppId()); - o.setCode(dbChannel.getCode()); - }); - - // 调用, 并断言异常 - assertServiceException(() -> channelService.createChannel(reqVO), CHANNEL_EXIST_SAME_CHANNEL_ERROR); - } - - @Test - public void testUpdateChannel_success() { - // mock 数据 - PayChannelDO dbChannel = randomPojo(PayChannelDO.class, o -> { - o.setCode(PayChannelEnum.ALIPAY_APP.getCode()); - o.setConfig(randomAlipayPayClientConfig()); - }); - channelMapper.insert(dbChannel);// @Sql: 先插入出一条存在的数据 - // 准备参数 - AlipayPayClientConfig config = randomAlipayPayClientConfig(); - PayChannelUpdateReqVO reqVO = randomPojo(PayChannelUpdateReqVO.class, o -> { - o.setId(dbChannel.getId()); // 设置更新的 ID - o.setStatus(randomCommonStatus()); - o.setConfig(JsonUtils.toJsonString(config)); - }); - - // 调用 - channelService.updateChannel(reqVO); - // 校验是否更新正确 - PayChannelDO channel = channelMapper.selectById(reqVO.getId()); // 获取最新的 - assertPojoEquals(reqVO, channel, "config"); - assertPojoEquals(config, channel.getConfig()); - } - - @Test - public void testUpdateChannel_notExists() { - // 准备参数 - AlipayPayClientConfig payClientPublicKeyConfig = randomAlipayPayClientConfig(); - PayChannelUpdateReqVO reqVO = randomPojo(PayChannelUpdateReqVO.class, o -> { - o.setStatus(CommonStatusEnum.ENABLE.getStatus()); - o.setConfig(JSON.toJSONString(payClientPublicKeyConfig)); - }); - - // 调用, 并断言异常 - assertServiceException(() -> channelService.updateChannel(reqVO), CHANNEL_NOT_FOUND); - } - - @Test - public void testDeleteChannel_success() { - // mock 数据 - PayChannelDO dbChannel = randomPojo(PayChannelDO.class, o -> { - o.setCode(PayChannelEnum.ALIPAY_APP.getCode()); - o.setConfig(randomAlipayPayClientConfig()); - }); - channelMapper.insert(dbChannel);// @Sql: 先插入出一条存在的数据 - // 准备参数 - Long id = dbChannel.getId(); - - // 调用 - channelService.deleteChannel(id); - // 校验数据不存在了 - assertNull(channelMapper.selectById(id)); - } - - @Test - public void testDeleteChannel_notExists() { - // 准备参数 - Long id = randomLongId(); - - // 调用, 并断言异常 - assertServiceException(() -> channelService.deleteChannel(id), CHANNEL_NOT_FOUND); - } - - @Test - public void testGetChannel() { - // mock 数据 - PayChannelDO dbChannel = randomPojo(PayChannelDO.class, o -> { - o.setCode(PayChannelEnum.ALIPAY_APP.getCode()); - o.setConfig(randomAlipayPayClientConfig()); - }); - channelMapper.insert(dbChannel);// @Sql: 先插入出一条存在的数据 - // 准备参数 - Long id = dbChannel.getId(); - - // 调用 - PayChannelDO channel = channelService.getChannel(id); - // 校验是否更新正确 - assertPojoEquals(dbChannel, channel); - } - - @Test - public void testGetChannelListByAppIds() { - // mock 数据 - PayChannelDO dbChannel01 = randomPojo(PayChannelDO.class, o -> { - o.setCode(PayChannelEnum.ALIPAY_APP.getCode()); - o.setConfig(randomAlipayPayClientConfig()); - }); - channelMapper.insert(dbChannel01);// @Sql: 先插入出一条存在的数据 - PayChannelDO dbChannel02 = randomPojo(PayChannelDO.class, o -> { - o.setCode(PayChannelEnum.WX_PUB.getCode()); - o.setConfig(randomWxPayClientConfig()); - }); - channelMapper.insert(dbChannel02);// @Sql: 先插入出一条存在的数据 - // 准备参数 - Long appId = dbChannel01.getAppId(); - - // 调用 - List channels = channelService.getChannelListByAppIds(Collections.singleton(appId)); - // 校验是否更新正确 - assertEquals(1, channels.size()); - assertPojoEquals(dbChannel01, channels.get(0)); - } - - @Test - public void testGetChannelByAppIdAndCode() { - // mock 数据 - PayChannelDO dbChannel = randomPojo(PayChannelDO.class, o -> { - o.setCode(PayChannelEnum.ALIPAY_APP.getCode()); - o.setConfig(randomAlipayPayClientConfig()); - }); - channelMapper.insert(dbChannel);// @Sql: 先插入出一条存在的数据 - // 准备参数 - Long appId = dbChannel.getAppId(); - String code = dbChannel.getCode(); - - // 调用 - PayChannelDO channel = channelService.getChannelByAppIdAndCode(appId, code); - // 断言 - assertPojoEquals(channel, dbChannel); - } - - @Test - public void testValidPayChannel_notExists() { - // 准备参数 - Long id = randomLongId(); - - // 调用, 并断言异常 - assertServiceException(() -> channelService.validPayChannel(id), CHANNEL_NOT_FOUND); - } - - @Test - public void testValidPayChannel_isDisable() { - // mock 数据 - PayChannelDO dbChannel = randomPojo(PayChannelDO.class, o -> { - o.setCode(PayChannelEnum.ALIPAY_APP.getCode()); - o.setConfig(randomAlipayPayClientConfig()); - o.setStatus(CommonStatusEnum.DISABLE.getStatus()); - }); - channelMapper.insert(dbChannel);// @Sql: 先插入出一条存在的数据 - // 准备参数 - Long id = dbChannel.getId(); - - // 调用, 并断言异常 - assertServiceException(() -> channelService.validPayChannel(id), CHANNEL_IS_DISABLE); - } - - @Test - public void testValidPayChannel_success() { - // mock 数据 - PayChannelDO dbChannel = randomPojo(PayChannelDO.class, o -> { - o.setCode(PayChannelEnum.ALIPAY_APP.getCode()); - o.setConfig(randomAlipayPayClientConfig()); - o.setStatus(CommonStatusEnum.ENABLE.getStatus()); - }); - channelMapper.insert(dbChannel);// @Sql: 先插入出一条存在的数据 - // 准备参数 - Long id = dbChannel.getId(); - - // 调用 - PayChannelDO channel = channelService.validPayChannel(id); - // 断言异常 - assertPojoEquals(channel, dbChannel); - } - - @Test - public void testValidPayChannel_appIdAndCode() { - // mock 数据 - PayChannelDO dbChannel = randomPojo(PayChannelDO.class, o -> { - o.setCode(PayChannelEnum.ALIPAY_APP.getCode()); - o.setConfig(randomAlipayPayClientConfig()); - o.setStatus(CommonStatusEnum.ENABLE.getStatus()); - }); - channelMapper.insert(dbChannel);// @Sql: 先插入出一条存在的数据 - // 准备参数 - Long appId = dbChannel.getAppId(); - String code = dbChannel.getCode(); - - // 调用 - PayChannelDO channel = channelService.validPayChannel(appId, code); - // 断言异常 - assertPojoEquals(channel, dbChannel); - } - - @Test - public void testGetEnableChannelList() { - // 准备参数 - Long appId = randomLongId(); - // mock 数据 01(enable 不匹配) - PayChannelDO dbChannel01 = randomPojo(PayChannelDO.class, o -> { - o.setCode(PayChannelEnum.ALIPAY_APP.getCode()); - o.setConfig(randomAlipayPayClientConfig()); - o.setStatus(CommonStatusEnum.DISABLE.getStatus()); - }); - channelMapper.insert(dbChannel01);// @Sql: 先插入出一条存在的数据 - // mock 数据 02(appId 不匹配) - PayChannelDO dbChannel02 = randomPojo(PayChannelDO.class, o -> { - o.setCode(PayChannelEnum.ALIPAY_APP.getCode()); - o.setConfig(randomAlipayPayClientConfig()); - o.setStatus(CommonStatusEnum.ENABLE.getStatus()); - }); - channelMapper.insert(dbChannel02);// @Sql: 先插入出一条存在的数据 - // mock 数据 03 - PayChannelDO dbChannel03 = randomPojo(PayChannelDO.class, o -> { - o.setCode(PayChannelEnum.ALIPAY_APP.getCode()); - o.setConfig(randomAlipayPayClientConfig()); - o.setAppId(appId); - o.setStatus(CommonStatusEnum.ENABLE.getStatus()); - }); - channelMapper.insert(dbChannel03);// @Sql: 先插入出一条存在的数据 - - // 调用 - List channel = channelService.getEnableChannelList(appId); - // 断言异常 - assertPojoEquals(channel, dbChannel03); - } - - @Test - public void testGetPayClient() { - // mock 数据 - PayChannelDO channel = randomPojo(PayChannelDO.class, o -> { - o.setCode(PayChannelEnum.ALIPAY_APP.getCode()); - o.setConfig(randomAlipayPayClientConfig()); - o.setStatus(CommonStatusEnum.ENABLE.getStatus()); - }); - channelMapper.insert(channel); - // mock 参数 - Long id = channel.getId(); - // mock 方法 - PayClient mockClient = mock(PayClient.class); - when(payClientFactory.createOrUpdatePayClient(eq(id), eq(channel.getCode()), eq(channel.getConfig()))) - .thenReturn(mockClient); - - // 调用 - PayClient client = channelService.getPayClient(id); - // 断言 - assertSame(client, mockClient); - } - - public WxPayClientConfig randomWxPayClientConfig() { - return new WxPayClientConfig() - .setAppId(randomString()) - .setMchId(randomString()) - .setApiVersion(WxPayClientConfig.API_VERSION_V2) - .setMchKey(randomString()); - } - - public AlipayPayClientConfig randomAlipayPayClientConfig() { - return new AlipayPayClientConfig() - .setServerUrl(randomURL()) - .setAppId(randomString()) - .setSignType(AlipayPayClientConfig.SIGN_TYPE_DEFAULT) - .setMode(AlipayPayClientConfig.MODE_PUBLIC_KEY) - .setPrivateKey(randomString()) - .setAlipayPublicKey(randomString()); - } - -} diff --git a/yudao-module-pay/yudao-module-pay-biz/src/test/java/cn/iocoder/yudao/module/pay/service/notify/PayNotifyServiceTest.java b/yudao-module-pay/yudao-module-pay-biz/src/test/java/cn/iocoder/yudao/module/pay/service/notify/PayNotifyServiceTest.java deleted file mode 100644 index cfd01ae..0000000 --- a/yudao-module-pay/yudao-module-pay-biz/src/test/java/cn/iocoder/yudao/module/pay/service/notify/PayNotifyServiceTest.java +++ /dev/null @@ -1,353 +0,0 @@ -package cn.iocoder.yudao.module.pay.service.notify; - -import cn.hutool.extra.spring.SpringUtil; -import cn.iocoder.yudao.framework.common.pojo.CommonResult; -import cn.iocoder.yudao.framework.common.pojo.PageResult; -import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest; -import cn.iocoder.yudao.module.pay.controller.admin.notify.vo.PayNotifyTaskPageReqVO; -import cn.iocoder.yudao.module.pay.dal.dataobject.notify.PayNotifyLogDO; -import cn.iocoder.yudao.module.pay.dal.dataobject.notify.PayNotifyTaskDO; -import cn.iocoder.yudao.module.pay.dal.dataobject.order.PayOrderDO; -import cn.iocoder.yudao.module.pay.dal.dataobject.refund.PayRefundDO; -import cn.iocoder.yudao.module.pay.dal.mysql.notify.PayNotifyLogMapper; -import cn.iocoder.yudao.module.pay.dal.mysql.notify.PayNotifyTaskMapper; -import cn.iocoder.yudao.module.pay.dal.redis.notify.PayNotifyLockRedisDAO; -import cn.iocoder.yudao.module.pay.enums.notify.PayNotifyStatusEnum; -import cn.iocoder.yudao.module.pay.enums.notify.PayNotifyTypeEnum; -import cn.iocoder.yudao.module.pay.framework.job.config.PayJobConfiguration; -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.refund.PayRefundServiceImpl; -import org.junit.jupiter.api.Disabled; -import org.junit.jupiter.api.Test; -import org.mockito.MockedStatic; -import org.redisson.api.RLock; -import org.redisson.api.RedissonClient; -import org.springframework.boot.test.mock.mockito.MockBean; -import org.springframework.context.annotation.Import; - -import jakarta.annotation.Resource; -import java.time.Duration; -import java.util.List; - -import static cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants.BAD_REQUEST; -import static cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.*; -import static cn.iocoder.yudao.framework.common.util.object.ObjectUtils.cloneIgnoreId; -import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertPojoEquals; -import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo; -import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomString; -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.*; -import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.*; - -/** - * {@link PayRefundServiceImpl} 的单元测试类 - * - * @author 芋艿 - */ -@Disabled // TODO 芋艿:后续 fix 补充的单测 -@Import({PayJobConfiguration.class, PayNotifyServiceImpl.class, PayNotifyLockRedisDAO.class}) -public class PayNotifyServiceTest extends BaseDbUnitTest { - - @Resource - private PayNotifyServiceImpl notifyService; - - @MockBean - private PayOrderService orderService; - @MockBean - private PayRefundService refundService; - - @Resource - private PayNotifyTaskMapper notifyTaskMapper; - @Resource - private PayNotifyLogMapper notifyLogMapper; - - @MockBean - private RedissonClient redissonClient; - - @Test - public void testCreatePayNotifyTask_order() { - PayNotifyServiceImpl payNotifyService = mock(PayNotifyServiceImpl.class); - try (MockedStatic springUtilMockedStatic = mockStatic(SpringUtil.class)) { - springUtilMockedStatic.when(() -> SpringUtil.getBean(eq(PayNotifyServiceImpl.class))) - .thenReturn(payNotifyService); - - // 准备参数 - Integer type = PayNotifyTypeEnum.ORDER.getType(); - Long dataId = 1L; - // mock 方法(order) - PayOrderDO order = randomPojo(PayOrderDO.class); - when(orderService.getOrder(eq(1L))).thenReturn(order); - // mock 方法(lock) - mockLock(null); // null 的原因,是咱没办法拿到 taskId 新增 - - // 调用 - notifyService.createPayNotifyTask(type, dataId); - // 断言,task - PayNotifyTaskDO dbTask = notifyTaskMapper.selectOne(null); - assertNotNull(dbTask.getNextNotifyTime()); - assertThat(dbTask) - .extracting("type", "dataId", "status", "notifyTimes", "maxNotifyTimes", - "appId", "merchantOrderId", "notifyUrl") - .containsExactly(type, dataId, PayNotifyStatusEnum.WAITING.getStatus(), 0, 9, - order.getAppId(), order.getMerchantOrderId(), order.getNotifyUrl()); - // 断言,调用 - verify(payNotifyService).executeNotify0(eq(dbTask)); - } - } - - @Test - public void testCreatePayNotifyTask_refund() { - PayNotifyServiceImpl payNotifyService = mock(PayNotifyServiceImpl.class); - try (MockedStatic springUtilMockedStatic = mockStatic(SpringUtil.class)) { - springUtilMockedStatic.when(() -> SpringUtil.getBean(eq(PayNotifyServiceImpl.class))) - .thenReturn(payNotifyService); - - // 准备参数 - Integer type = PayNotifyTypeEnum.REFUND.getType(); - Long dataId = 1L; - // mock 方法(refund) - PayRefundDO refund = randomPojo(PayRefundDO.class); - when(refundService.getRefund(eq(1L))).thenReturn(refund); - // mock 方法(lock) - mockLock(null); // null 的原因,是咱没办法拿到 taskId 新增 - - // 调用 - notifyService.createPayNotifyTask(type, dataId); - // 断言,task - PayNotifyTaskDO dbTask = notifyTaskMapper.selectOne(null); - assertNotNull(dbTask.getNextNotifyTime()); - assertThat(dbTask) - .extracting("type", "dataId", "status", "notifyTimes", "maxNotifyTimes", - "appId", "merchantOrderId", "notifyUrl") - .containsExactly(type, dataId, PayNotifyStatusEnum.WAITING.getStatus(), 0, 9, - refund.getAppId(), refund.getMerchantOrderId(), refund.getNotifyUrl()); - // 断言,调用 - verify(payNotifyService).executeNotify0(eq(dbTask)); - } - } - - @Test - public void testExecuteNotify() throws InterruptedException { - // mock 数据(notify) - PayNotifyTaskDO dbTask01 = randomPojo(PayNotifyTaskDO.class, - o -> o.setStatus(PayNotifyStatusEnum.WAITING.getStatus()) - .setNextNotifyTime(addTime(Duration.ofMinutes(-1)))); - notifyTaskMapper.insert(dbTask01); - PayNotifyTaskDO dbTask02 = randomPojo(PayNotifyTaskDO.class, - o -> o.setStatus(PayNotifyStatusEnum.REQUEST_SUCCESS.getStatus()) - .setNextNotifyTime(addTime(Duration.ofMinutes(-1)))); - notifyTaskMapper.insert(dbTask02); - PayNotifyTaskDO dbTask03 = randomPojo(PayNotifyTaskDO.class, - o -> o.setStatus(PayNotifyStatusEnum.REQUEST_FAILURE.getStatus()) - .setNextNotifyTime(addTime(Duration.ofMinutes(-1)))); - notifyTaskMapper.insert(dbTask03); - PayNotifyTaskDO dbTask04 = randomPojo(PayNotifyTaskDO.class, // 不满足状态 - o -> o.setStatus(PayNotifyStatusEnum.FAILURE.getStatus()) - .setNextNotifyTime(addTime(Duration.ofMinutes(-1)))); - notifyTaskMapper.insert(dbTask04); - PayNotifyTaskDO dbTask05 = randomPojo(PayNotifyTaskDO.class, // 不满足状态 - o -> o.setStatus(PayNotifyStatusEnum.SUCCESS.getStatus()) - .setNextNotifyTime(addTime(Duration.ofMinutes(-1)))); - notifyTaskMapper.insert(dbTask05); - PayNotifyTaskDO dbTask06 = randomPojo(PayNotifyTaskDO.class, // 不满足时间 - o -> o.setStatus(PayNotifyStatusEnum.SUCCESS.getStatus()) - .setNextNotifyTime(addTime(Duration.ofMinutes(1)))); - notifyTaskMapper.insert(dbTask06); - // mock 方法(lock) - mockLock(dbTask01.getId()); - mockLock(dbTask02.getId()); - mockLock(dbTask03.getId()); - - // 调用 - int count = notifyService.executeNotify(); - // 断言,数量 - assertEquals(count, 3); - } - - @Test // 由于 HttpUtil 不好 mock,所以只测试异常的情况 - public void testExecuteNotify0_exception() { - // mock 数据(task) - PayNotifyTaskDO task = randomPojo(PayNotifyTaskDO.class, o -> o.setType(-1) - .setNotifyTimes(0).setMaxNotifyTimes(9)); - notifyTaskMapper.insert(task); - - // 调用 - notifyService.executeNotify0(task); - // 断言,task - PayNotifyTaskDO dbTask = notifyTaskMapper.selectById(task.getId()); - assertNotEquals(task.getNextNotifyTime(), dbTask.getNextNotifyTime()); - assertNotEquals(task.getLastExecuteTime(), dbTask.getNextNotifyTime()); - assertEquals(dbTask.getNotifyTimes(), 1); - assertEquals(dbTask.getStatus(), PayNotifyStatusEnum.REQUEST_FAILURE.getStatus()); - // 断言,log - PayNotifyLogDO dbLog = notifyLogMapper.selectOne(null); - assertEquals(dbLog.getTaskId(), task.getId()); - assertEquals(dbLog.getNotifyTimes(), 1); - assertTrue(dbLog.getResponse().contains("未知的通知任务类型:")); - assertEquals(dbLog.getStatus(), PayNotifyStatusEnum.REQUEST_FAILURE.getStatus()); - } - - @Test - public void testProcessNotifyResult_success() { - // mock 数据(task) - PayNotifyTaskDO task = randomPojo(PayNotifyTaskDO.class, - o -> o.setNotifyTimes(0).setMaxNotifyTimes(9)); - notifyTaskMapper.insert(task); - // 准备参数 - CommonResult invokeResult = CommonResult.success(randomString()); - - // 调用 - notifyService.processNotifyResult(task, invokeResult, null); - // 断言 - PayNotifyTaskDO dbTask = notifyTaskMapper.selectById(task.getId()); - assertEquals(task.getNextNotifyTime(), dbTask.getNextNotifyTime()); - assertNotEquals(task.getLastExecuteTime(), dbTask.getNextNotifyTime()); - assertEquals(dbTask.getNotifyTimes(), 1); - assertEquals(dbTask.getStatus(), PayNotifyStatusEnum.SUCCESS.getStatus()); - } - - @Test - public void testProcessNotifyResult_failure() { - // mock 数据(task) - PayNotifyTaskDO task = randomPojo(PayNotifyTaskDO.class, - o -> o.setNotifyTimes(8).setMaxNotifyTimes(9)); - notifyTaskMapper.insert(task); - // 准备参数 - CommonResult invokeResult = CommonResult.error(BAD_REQUEST); - - // 调用 - notifyService.processNotifyResult(task, invokeResult, null); - // 断言 - PayNotifyTaskDO dbTask = notifyTaskMapper.selectById(task.getId()); - assertEquals(task.getNextNotifyTime(), dbTask.getNextNotifyTime()); - assertNotEquals(task.getLastExecuteTime(), dbTask.getNextNotifyTime()); - assertEquals(dbTask.getNotifyTimes(), 9); - assertEquals(dbTask.getStatus(), PayNotifyStatusEnum.FAILURE.getStatus()); - } - - @Test - public void testProcessNotifyResult_requestFailure() { - // mock 数据(task) - PayNotifyTaskDO task = randomPojo(PayNotifyTaskDO.class, - o -> o.setNotifyTimes(0).setMaxNotifyTimes(9)); - notifyTaskMapper.insert(task); - // 准备参数 - CommonResult invokeResult = CommonResult.error(BAD_REQUEST); - - // 调用 - notifyService.processNotifyResult(task, invokeResult, null); - // 断言 - PayNotifyTaskDO dbTask = notifyTaskMapper.selectById(task.getId()); - assertNotEquals(task.getNextNotifyTime(), dbTask.getNextNotifyTime()); - assertNotEquals(task.getLastExecuteTime(), dbTask.getNextNotifyTime()); - assertEquals(dbTask.getNotifyTimes(), 1); - assertEquals(dbTask.getStatus(), PayNotifyStatusEnum.REQUEST_SUCCESS.getStatus()); - } - - @Test - public void testProcessNotifyResult_requestSuccess() { - // mock 数据(task) - PayNotifyTaskDO task = randomPojo(PayNotifyTaskDO.class, - o -> o.setNotifyTimes(0).setMaxNotifyTimes(9)); - notifyTaskMapper.insert(task); - // 准备参数 - CommonResult invokeResult = CommonResult.error(BAD_REQUEST); - RuntimeException invokeException = new RuntimeException(); - - // 调用 - notifyService.processNotifyResult(task, invokeResult, invokeException); - // 断言 - PayNotifyTaskDO dbTask = notifyTaskMapper.selectById(task.getId()); - assertNotEquals(task.getNextNotifyTime(), dbTask.getNextNotifyTime()); - assertNotEquals(task.getLastExecuteTime(), dbTask.getNextNotifyTime()); - assertEquals(dbTask.getNotifyTimes(), 1); - assertEquals(dbTask.getStatus(), PayNotifyStatusEnum.REQUEST_FAILURE.getStatus()); - } - - @Test - public void testGetNotifyTask() { - // mock 数据(task) - PayNotifyTaskDO task = randomPojo(PayNotifyTaskDO.class); - notifyTaskMapper.insert(task); - // 准备参数 - Long id = task.getId(); - - // 调用 - PayNotifyTaskDO dbTask = notifyService.getNotifyTask(id); - // 断言 - assertPojoEquals(dbTask, task); - } - - @Test - public void testGetNotifyTaskPage() { - // mock 数据 - PayNotifyTaskDO dbTask = randomPojo(PayNotifyTaskDO.class, o -> { // 等会查询到 - o.setAppId(1L); - o.setType(PayNotifyTypeEnum.REFUND.getType()); - o.setDataId(100L); - o.setStatus(PayNotifyStatusEnum.SUCCESS.getStatus()); - o.setMerchantOrderId("P110"); - o.setCreateTime(buildTime(2023, 2, 3)); - }); - notifyTaskMapper.insert(dbTask); - // 测试 appId 不匹配 - notifyTaskMapper.insert(cloneIgnoreId(dbTask, o -> o.setAppId(2L))); - // 测试 type 不匹配 - notifyTaskMapper.insert(cloneIgnoreId(dbTask, o -> o.setType(PayNotifyTypeEnum.ORDER.getType()))); - // 测试 dataId 不匹配 - notifyTaskMapper.insert(cloneIgnoreId(dbTask, o -> o.setDataId(200L))); - // 测试 status 不匹配 - notifyTaskMapper.insert(cloneIgnoreId(dbTask, o -> o.setStatus(PayNotifyStatusEnum.FAILURE.getStatus()))); - // 测试 merchantOrderId 不匹配 - notifyTaskMapper.insert(cloneIgnoreId(dbTask, o -> o.setMerchantOrderId(randomString()))); - // 测试 createTime 不匹配 - notifyTaskMapper.insert(cloneIgnoreId(dbTask, o -> o.setCreateTime(buildTime(2023, 1, 1)))); - // 准备参数 - PayNotifyTaskPageReqVO reqVO = new PayNotifyTaskPageReqVO(); - reqVO.setAppId(1L); - reqVO.setType(PayNotifyTypeEnum.REFUND.getType()); - reqVO.setDataId(100L); - reqVO.setStatus(PayNotifyStatusEnum.SUCCESS.getStatus()); - reqVO.setMerchantOrderId("P110"); - reqVO.setCreateTime(buildBetweenTime(2023, 2, 1, 2023, 2, 28)); - - // 调用 - PageResult pageResult = notifyService.getNotifyTaskPage(reqVO); - // 断言 - assertEquals(1, pageResult.getTotal()); - assertEquals(1, pageResult.getList().size()); - assertPojoEquals(dbTask, pageResult.getList().get(0)); - } - - @Test - public void testGetNotifyLogList() { - // mock 数据 - PayNotifyLogDO dbLog = randomPojo(PayNotifyLogDO.class); - notifyLogMapper.insert(dbLog); - PayNotifyLogDO dbLog02 = randomPojo(PayNotifyLogDO.class); - notifyLogMapper.insert(dbLog02); - // 准备参数 - Long taskId = dbLog.getTaskId(); - - // 调用 - List logList = notifyService.getNotifyLogList(taskId); - // 断言 - assertEquals(logList.size(), 1); - assertPojoEquals(dbLog, logList.get(0)); - } - - private void mockLock(Long id) { - RLock lock = mock(RLock.class); - if (id == null) { - when(redissonClient.getLock(anyString())) - .thenReturn(lock); - } else { - when(redissonClient.getLock(eq("pay_notify:lock:" + id))) - .thenReturn(lock); - } - } - -} 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 deleted file mode 100755 index d5e449a..0000000 --- a/yudao-module-pay/yudao-module-pay-biz/src/test/java/cn/iocoder/yudao/module/pay/service/order/PayOrderServiceTest.java +++ /dev/null @@ -1,1104 +0,0 @@ -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.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; -import cn.iocoder.yudao.module.pay.controller.admin.order.vo.PayOrderPageReqVO; -import cn.iocoder.yudao.module.pay.controller.admin.order.vo.PayOrderSubmitReqVO; -import cn.iocoder.yudao.module.pay.controller.admin.order.vo.PayOrderSubmitRespVO; -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; -import cn.iocoder.yudao.module.pay.dal.dataobject.order.PayOrderExtensionDO; -import cn.iocoder.yudao.module.pay.dal.mysql.order.PayOrderExtensionMapper; -import cn.iocoder.yudao.module.pay.dal.mysql.order.PayOrderMapper; -import cn.iocoder.yudao.module.pay.dal.redis.no.PayNoRedisDAO; -import cn.iocoder.yudao.module.pay.enums.notify.PayNotifyTypeEnum; -import cn.iocoder.yudao.module.pay.enums.order.PayOrderStatusEnum; -import cn.iocoder.yudao.module.pay.framework.pay.config.PayProperties; -import cn.iocoder.yudao.module.pay.service.app.PayAppService; -import cn.iocoder.yudao.module.pay.service.channel.PayChannelService; -import cn.iocoder.yudao.module.pay.service.notify.PayNotifyService; -import jakarta.annotation.Resource; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.mockito.MockedStatic; -import org.springframework.boot.test.mock.mockito.MockBean; -import org.springframework.context.annotation.Import; - -import java.time.Duration; -import java.time.LocalDateTime; -import java.util.List; - -import static cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.*; -import static cn.iocoder.yudao.framework.common.util.json.JsonUtils.toJsonString; -import static cn.iocoder.yudao.framework.common.util.object.ObjectUtils.cloneIgnoreId; -import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertPojoEquals; -import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertServiceException; -import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.*; -import static cn.iocoder.yudao.module.pay.enums.ErrorCodeConstants.*; -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.mockito.ArgumentMatchers.*; -import static org.mockito.Mockito.*; - -/** - * {@link PayOrderServiceImpl} 的单元测试类 - * - * @author 芋艿 - */ -@Import({PayOrderServiceImpl.class, PayNoRedisDAO.class}) -public class PayOrderServiceTest extends BaseDbAndRedisUnitTest { - - @Resource - private PayOrderServiceImpl orderService; - - @Resource - private PayOrderMapper orderMapper; - @Resource - private PayOrderExtensionMapper orderExtensionMapper; - - @MockBean - private PayProperties properties; - @MockBean - private PayAppService appService; - @MockBean - private PayChannelService channelService; - @MockBean - private PayNotifyService notifyService; - - @BeforeEach - public void setUp() { - when(properties.getOrderNotifyUrl()).thenReturn("http://127.0.0.1"); - } - - @Test - public void testGetOrder_id() { - // mock 数据(PayOrderDO) - PayOrderDO order = randomPojo(PayOrderDO.class); - orderMapper.insert(order); - // 准备参数 - Long id = order.getId(); - - // 调用 - PayOrderDO dbOrder = orderService.getOrder(id); - // 断言 - assertPojoEquals(dbOrder, order); - } - - @Test - public void testGetOrder_appIdAndMerchantOrderId() { - // mock 数据(PayOrderDO) - PayOrderDO order = randomPojo(PayOrderDO.class); - orderMapper.insert(order); - // 准备参数 - Long appId = order.getAppId(); - String merchantOrderId = order.getMerchantOrderId(); - - // 调用 - PayOrderDO dbOrder = orderService.getOrder(appId, merchantOrderId); - // 断言 - assertPojoEquals(dbOrder, order); - } - - @Test - public void testGetOrderCountByAppId() { - // mock 数据(PayOrderDO) - PayOrderDO order01 = randomPojo(PayOrderDO.class); - orderMapper.insert(order01); - PayOrderDO order02 = randomPojo(PayOrderDO.class); - orderMapper.insert(order02); - // 准备参数 - Long appId = order01.getAppId(); - - // 调用 - Long count = orderService.getOrderCountByAppId(appId); - // 断言 - assertEquals(count, 1L); - } - - @Test - public void testGetOrderPage() { - // mock 数据 - PayOrderDO dbOrder = randomPojo(PayOrderDO.class, o -> { // 等会查询到 - o.setAppId(1L); - o.setChannelCode(PayChannelEnum.WX_PUB.getCode()); - o.setMerchantOrderId("110"); - o.setChannelOrderNo("220"); - o.setNo("330"); - o.setStatus(PayOrderStatusEnum.SUCCESS.getStatus()); - o.setCreateTime(buildTime(2018, 1, 15)); - }); - orderMapper.insert(dbOrder); - // 测试 appId 不匹配 - orderMapper.insert(cloneIgnoreId(dbOrder, o -> o.setAppId(2L))); - // 测试 channelCode 不匹配 - orderMapper.insert(cloneIgnoreId(dbOrder, o -> o.setChannelCode(PayChannelEnum.ALIPAY_APP.getCode()))); - // 测试 merchantOrderId 不匹配 - orderMapper.insert(cloneIgnoreId(dbOrder, o -> o.setMerchantOrderId(randomString()))); - // 测试 channelOrderNo 不匹配 - orderMapper.insert(cloneIgnoreId(dbOrder, o -> o.setChannelOrderNo(randomString()))); - // 测试 no 不匹配 - orderMapper.insert(cloneIgnoreId(dbOrder, o -> o.setNo(randomString()))); - // 测试 status 不匹配 - orderMapper.insert(cloneIgnoreId(dbOrder, o -> o.setStatus(PayOrderStatusEnum.CLOSED.getStatus()))); - // 测试 createTime 不匹配 - orderMapper.insert(cloneIgnoreId(dbOrder, o -> o.setCreateTime(buildTime(2019, 1, 1)))); - // 准备参数 - PayOrderPageReqVO reqVO = new PayOrderPageReqVO(); - reqVO.setAppId(1L); - reqVO.setChannelCode(PayChannelEnum.WX_PUB.getCode()); - reqVO.setMerchantOrderId("11"); - reqVO.setChannelOrderNo("22"); - reqVO.setNo("33"); - reqVO.setStatus(PayOrderStatusEnum.SUCCESS.getStatus()); - reqVO.setCreateTime(buildBetweenTime(2018, 1, 10, 2018, 1, 30)); - - // 调用 - PageResult pageResult = orderService.getOrderPage(reqVO); - // 断言 - assertEquals(1, pageResult.getTotal()); - assertEquals(1, pageResult.getList().size()); - assertPojoEquals(dbOrder, pageResult.getList().get(0)); - } - - @Test - public void testGetOrderList() { - // mock 数据 - PayOrderDO dbOrder = randomPojo(PayOrderDO.class, o -> { // 等会查询到 - o.setAppId(1L); - o.setChannelCode(PayChannelEnum.WX_PUB.getCode()); - o.setMerchantOrderId("110"); - o.setChannelOrderNo("220"); - o.setNo("330"); - o.setStatus(PayOrderStatusEnum.SUCCESS.getStatus()); - o.setCreateTime(buildTime(2018, 1, 15)); - }); - orderMapper.insert(dbOrder); - // 测试 appId 不匹配 - orderMapper.insert(cloneIgnoreId(dbOrder, o -> o.setAppId(2L))); - // 测试 channelCode 不匹配 - orderMapper.insert(cloneIgnoreId(dbOrder, o -> o.setChannelCode(PayChannelEnum.ALIPAY_APP.getCode()))); - // 测试 merchantOrderId 不匹配 - orderMapper.insert(cloneIgnoreId(dbOrder, o -> o.setMerchantOrderId(randomString()))); - // 测试 channelOrderNo 不匹配 - orderMapper.insert(cloneIgnoreId(dbOrder, o -> o.setChannelOrderNo(randomString()))); - // 测试 no 不匹配 - orderMapper.insert(cloneIgnoreId(dbOrder, o -> o.setNo(randomString()))); - // 测试 status 不匹配 - orderMapper.insert(cloneIgnoreId(dbOrder, o -> o.setStatus(PayOrderStatusEnum.CLOSED.getStatus()))); - // 测试 createTime 不匹配 - orderMapper.insert(cloneIgnoreId(dbOrder, o -> o.setCreateTime(buildTime(2019, 1, 1)))); - // 准备参数 - PayOrderExportReqVO reqVO = new PayOrderExportReqVO(); - reqVO.setAppId(1L); - reqVO.setChannelCode(PayChannelEnum.WX_PUB.getCode()); - reqVO.setMerchantOrderId("11"); - reqVO.setChannelOrderNo("22"); - reqVO.setNo("33"); - reqVO.setStatus(PayOrderStatusEnum.SUCCESS.getStatus()); - reqVO.setCreateTime(buildBetweenTime(2018, 1, 10, 2018, 1, 30)); - - // 调用 - List list = orderService.getOrderList(reqVO); - // 断言 - assertEquals(1, list.size()); - assertPojoEquals(dbOrder, list.get(0)); - } - - @Test - public void testCreateOrder_success() { - // mock 参数 - PayOrderCreateReqDTO reqDTO = randomPojo(PayOrderCreateReqDTO.class, - o -> o.setAppKey("demo").setMerchantOrderId("10") - .setSubject(randomString()).setBody(randomString())); - // mock 方法 - PayAppDO app = randomPojo(PayAppDO.class, o -> o.setId(1L).setOrderNotifyUrl("http://127.0.0.1")); - when(appService.validPayApp(eq(reqDTO.getAppKey()))).thenReturn(app); - - // 调用 - Long orderId = orderService.createOrder(reqDTO); - // 断言 - PayOrderDO order = orderMapper.selectById(orderId); - assertPojoEquals(order, reqDTO); - assertEquals(order.getAppId(), 1L); - assertEquals(order.getNotifyUrl(), "http://127.0.0.1"); - assertEquals(order.getStatus(), PayOrderStatusEnum.WAITING.getStatus()); - assertEquals(order.getRefundPrice(), 0); - } - - @Test - public void testCreateOrder_exists() { - // mock 参数 - PayOrderCreateReqDTO reqDTO = randomPojo(PayOrderCreateReqDTO.class, - o -> o.setAppKey("demo").setMerchantOrderId("10")); - // mock 数据 - PayOrderDO dbOrder = randomPojo(PayOrderDO.class, o -> o.setAppId(1L).setMerchantOrderId("10")); - orderMapper.insert(dbOrder); - // mock 方法 - PayAppDO app = randomPojo(PayAppDO.class, o -> o.setId(1L).setOrderNotifyUrl("http://127.0.0.1")); - when(appService.validPayApp(eq(reqDTO.getAppKey()))).thenReturn(app); - - // 调用 - Long orderId = orderService.createOrder(reqDTO); - // 断言 - PayOrderDO order = orderMapper.selectById(orderId); - assertPojoEquals(dbOrder, order); - } - - @Test - public void testSubmitOrder_notFound() { - // 准备参数 - PayOrderSubmitReqVO reqVO = randomPojo(PayOrderSubmitReqVO.class); - String userIp = randomString(); - - // 调用, 并断言异常 - assertServiceException(() -> orderService.submitOrder(reqVO, userIp), PAY_ORDER_NOT_FOUND); - } - - @Test - public void testSubmitOrder_notWaiting() { - // mock 数据(order) - PayOrderDO order = randomPojo(PayOrderDO.class, o -> o.setStatus(PayOrderStatusEnum.REFUND.getStatus())); - orderMapper.insert(order); - // 准备参数 - PayOrderSubmitReqVO reqVO = randomPojo(PayOrderSubmitReqVO.class, o -> o.setId(order.getId())); - String userIp = randomString(); - - // 调用, 并断言异常 - assertServiceException(() -> orderService.submitOrder(reqVO, userIp), PAY_ORDER_STATUS_IS_NOT_WAITING); - } - - @Test - public void testSubmitOrder_isSuccess() { - // mock 数据(order) - PayOrderDO order = randomPojo(PayOrderDO.class, o -> o.setStatus(PayOrderStatusEnum.SUCCESS.getStatus())); - orderMapper.insert(order); - // 准备参数 - PayOrderSubmitReqVO reqVO = randomPojo(PayOrderSubmitReqVO.class, o -> o.setId(order.getId())); - String userIp = randomString(); - - // 调用, 并断言异常 - assertServiceException(() -> orderService.submitOrder(reqVO, userIp), PAY_ORDER_STATUS_IS_SUCCESS); - } - - @Test - public void testSubmitOrder_expired() { - // mock 数据(order) - PayOrderDO order = randomPojo(PayOrderDO.class, o -> o.setStatus(PayOrderStatusEnum.WAITING.getStatus()) - .setExpireTime(addTime(Duration.ofDays(-1)))); - orderMapper.insert(order); - // 准备参数 - PayOrderSubmitReqVO reqVO = randomPojo(PayOrderSubmitReqVO.class, o -> o.setId(order.getId())); - String userIp = randomString(); - - // 调用, 并断言异常 - assertServiceException(() -> orderService.submitOrder(reqVO, userIp), PAY_ORDER_IS_EXPIRED); - } - - @Test - public void testSubmitOrder_channelNotFound() { - // mock 数据(order) - PayOrderDO order = randomPojo(PayOrderDO.class, o -> o.setStatus(PayOrderStatusEnum.WAITING.getStatus()) - .setAppId(1L).setExpireTime(addTime(Duration.ofDays(1)))); - orderMapper.insert(order); - // 准备参数 - PayOrderSubmitReqVO reqVO = randomPojo(PayOrderSubmitReqVO.class, o -> o.setId(order.getId()) - .setChannelCode(PayChannelEnum.ALIPAY_APP.getCode())); - String userIp = randomString(); - // mock 方法(app) - PayAppDO app = randomPojo(PayAppDO.class, o -> o.setId(1L)); - when(appService.validPayApp(eq(1L))).thenReturn(app); - // mock 方法(channel) - PayChannelDO channel = randomPojo(PayChannelDO.class, o -> o.setCode(PayChannelEnum.ALIPAY_APP.getCode())); - when(channelService.validPayChannel(eq(1L), eq(PayChannelEnum.ALIPAY_APP.getCode()))) - .thenReturn(channel); - - // 调用, 并断言异常 - assertServiceException(() -> orderService.submitOrder(reqVO, userIp), CHANNEL_NOT_FOUND); - } - - @Test // 调用 unifiedOrder 接口,返回存在渠道错误 - public void testSubmitOrder_channelError() { - PayOrderServiceImpl payOrderServiceImpl = mock(PayOrderServiceImpl.class); - try (MockedStatic springUtilMockedStatic = mockStatic(SpringUtil.class)) { - springUtilMockedStatic.when(() -> SpringUtil.getBean(eq(PayOrderServiceImpl.class))) - .thenReturn(payOrderServiceImpl); - - // mock 数据(order) - PayOrderDO order = randomPojo(PayOrderDO.class, o -> o.setStatus(PayOrderStatusEnum.WAITING.getStatus()) - .setAppId(1L).setExpireTime(addTime(Duration.ofDays(1)))); - orderMapper.insert(order); - // 准备参数 - PayOrderSubmitReqVO reqVO = randomPojo(PayOrderSubmitReqVO.class, o -> o.setId(order.getId()) - .setChannelCode(PayChannelEnum.ALIPAY_APP.getCode())); - String userIp = randomString(); - // mock 方法(app) - PayAppDO app = randomPojo(PayAppDO.class, o -> o.setId(1L)); - when(appService.validPayApp(eq(1L))).thenReturn(app); - // mock 方法(channel) - PayChannelDO channel = randomPojo(PayChannelDO.class, o -> o.setId(10L) - .setCode(PayChannelEnum.ALIPAY_APP.getCode())); - when(channelService.validPayChannel(eq(1L), eq(PayChannelEnum.ALIPAY_APP.getCode()))) - .thenReturn(channel); - // mock 方法(client) - PayClient client = mock(PayClient.class); - when(channelService.getPayClient(eq(10L))).thenReturn(client); - // mock 方法() - PayOrderRespDTO unifiedOrderResp = randomPojo(PayOrderRespDTO.class, o -> - o.setChannelErrorCode("001").setChannelErrorMsg("模拟异常")); - when(client.unifiedOrder(argThat(payOrderUnifiedReqDTO -> { - assertNotNull(payOrderUnifiedReqDTO.getOutTradeNo()); - assertThat(payOrderUnifiedReqDTO) - .extracting("subject", "body", "notifyUrl", "returnUrl", "price", "expireTime") - .containsExactly(order.getSubject(), order.getBody(), "http://127.0.0.1/10", - reqVO.getReturnUrl(), order.getPrice(), order.getExpireTime()); - return true; - }))).thenReturn(unifiedOrderResp); - - // 调用,并断言异常 - assertServiceException(() -> orderService.submitOrder(reqVO, userIp), - PAY_ORDER_SUBMIT_CHANNEL_ERROR, "001", "模拟异常"); - // 断言,数据记录(PayOrderExtensionDO) - PayOrderExtensionDO orderExtension = orderExtensionMapper.selectOne(null); - assertNotNull(orderExtension); - assertThat(orderExtension).extracting("no", "orderId").isNotNull(); - assertThat(orderExtension) - .extracting("channelId", "channelCode","userIp" ,"status", "channelExtras", - "channelErrorCode", "channelErrorMsg", "channelNotifyData") - .containsExactly(10L, PayChannelEnum.ALIPAY_APP.getCode(), userIp, - PayOrderStatusEnum.WAITING.getStatus(), reqVO.getChannelExtras(), - null, null, null); - } - } - - @Test - public void testSubmitOrder_success() { - PayOrderServiceImpl payOrderServiceImpl = mock(PayOrderServiceImpl.class); - try (MockedStatic springUtilMockedStatic = mockStatic(SpringUtil.class)) { - springUtilMockedStatic.when(() -> SpringUtil.getBean(eq(PayOrderServiceImpl.class))) - .thenReturn(payOrderServiceImpl); - - // mock 数据(order) - PayOrderDO order = randomPojo(PayOrderDO.class, o -> o.setStatus(PayOrderStatusEnum.WAITING.getStatus()) - .setAppId(1L).setExpireTime(addTime(Duration.ofDays(1)))); - orderMapper.insert(order); - // 准备参数 - PayOrderSubmitReqVO reqVO = randomPojo(PayOrderSubmitReqVO.class, o -> o.setId(order.getId()) - .setChannelCode(PayChannelEnum.ALIPAY_APP.getCode())); - String userIp = randomString(); - // mock 方法(app) - PayAppDO app = randomPojo(PayAppDO.class, o -> o.setId(1L)); - when(appService.validPayApp(eq(1L))).thenReturn(app); - // mock 方法(channel) - PayChannelDO channel = randomPojo(PayChannelDO.class, o -> o.setId(10L) - .setCode(PayChannelEnum.ALIPAY_APP.getCode())); - when(channelService.validPayChannel(eq(1L), eq(PayChannelEnum.ALIPAY_APP.getCode()))) - .thenReturn(channel); - // mock 方法(client) - PayClient client = mock(PayClient.class); - when(channelService.getPayClient(eq(10L))).thenReturn(client); - // mock 方法(支付渠道的调用) - PayOrderRespDTO unifiedOrderResp = randomPojo(PayOrderRespDTO.class, o -> o.setChannelErrorCode(null).setChannelErrorMsg(null) - .setDisplayMode(PayOrderDisplayModeEnum.URL.getMode()).setDisplayContent("tudou")); - when(client.unifiedOrder(argThat(payOrderUnifiedReqDTO -> { - assertNotNull(payOrderUnifiedReqDTO.getOutTradeNo()); - assertThat(payOrderUnifiedReqDTO) - .extracting("subject", "body", "notifyUrl", "returnUrl", "price", "expireTime") - .containsExactly(order.getSubject(), order.getBody(), "http://127.0.0.1/10", - reqVO.getReturnUrl(), order.getPrice(), order.getExpireTime()); - return true; - }))).thenReturn(unifiedOrderResp); - - // 调用 - PayOrderSubmitRespVO result = orderService.submitOrder(reqVO, userIp); - // 断言,数据记录(PayOrderExtensionDO) - PayOrderExtensionDO orderExtension = orderExtensionMapper.selectOne(null); - assertNotNull(orderExtension); - assertThat(orderExtension).extracting("no", "orderId").isNotNull(); - assertThat(orderExtension) - .extracting("channelId", "channelCode","userIp" ,"status", "channelExtras", - "channelErrorCode", "channelErrorMsg", "channelNotifyData") - .containsExactly(10L, PayChannelEnum.ALIPAY_APP.getCode(), userIp, - PayOrderStatusEnum.WAITING.getStatus(), reqVO.getChannelExtras(), - null, null, null); - // 断言,返回(PayOrderSubmitRespVO) - assertThat(result) - .extracting("status", "displayMode", "displayContent") - .containsExactly(PayOrderStatusEnum.WAITING.getStatus(), PayOrderDisplayModeEnum.URL.getMode(), "tudou"); - // 断言,调用 - verify(payOrderServiceImpl).notifyOrder(same(channel), same(unifiedOrderResp)); - } - } - - @Test - public void testValidateOrderActuallyPaid_dbPaid() { - // 准备参数 - Long id = randomLongId(); - // mock 方法(OrderExtension 已支付) - PayOrderExtensionDO orderExtension = randomPojo(PayOrderExtensionDO.class, - o -> o.setOrderId(id).setStatus(PayOrderStatusEnum.SUCCESS.getStatus())); - orderExtensionMapper.insert(orderExtension); - - // 调用,并断言异常 - assertServiceException(() -> orderService.validateOrderActuallyPaid(id), - PAY_ORDER_EXTENSION_IS_PAID); - } - - @Test - public void testValidateOrderActuallyPaid_remotePaid() { - // 准备参数 - Long id = randomLongId(); - // mock 方法(OrderExtension 已支付) - PayOrderExtensionDO orderExtension = randomPojo(PayOrderExtensionDO.class, - o -> o.setOrderId(id).setStatus(PayOrderStatusEnum.WAITING.getStatus())); - orderExtensionMapper.insert(orderExtension); - // mock 方法(PayClient 已支付) - PayClient client = mock(PayClient.class); - when(channelService.getPayClient(eq(orderExtension.getChannelId()))).thenReturn(client); - when(client.getOrder(eq(orderExtension.getNo()))).thenReturn(randomPojo(PayOrderRespDTO.class, - o -> o.setStatus(PayOrderStatusEnum.SUCCESS.getStatus()))); - - // 调用,并断言异常 - assertServiceException(() -> orderService.validateOrderActuallyPaid(id), - PAY_ORDER_EXTENSION_IS_PAID); - } - - @Test - public void testValidateOrderActuallyPaid_success() { - // 准备参数 - Long id = randomLongId(); - // mock 方法(OrderExtension 已支付) - PayOrderExtensionDO orderExtension = randomPojo(PayOrderExtensionDO.class, - o -> o.setOrderId(id).setStatus(PayOrderStatusEnum.WAITING.getStatus())); - orderExtensionMapper.insert(orderExtension); - // mock 方法(PayClient 已支付) - PayClient client = mock(PayClient.class); - when(channelService.getPayClient(eq(orderExtension.getChannelId()))).thenReturn(client); - when(client.getOrder(eq(orderExtension.getNo()))).thenReturn(randomPojo(PayOrderRespDTO.class, - o -> o.setStatus(PayOrderStatusEnum.WAITING.getStatus()))); - - // 调用,并断言异常 - orderService.validateOrderActuallyPaid(id); - } - - @Test - public void testNotifyOrder_channelId() { - PayOrderServiceImpl payOrderServiceImpl = mock(PayOrderServiceImpl.class); - try (MockedStatic springUtilMockedStatic = mockStatic(SpringUtil.class)) { - springUtilMockedStatic.when(() -> SpringUtil.getBean(eq(PayOrderServiceImpl.class))) - .thenReturn(payOrderServiceImpl); - // 准备参数 - Long channelId = 10L; - PayOrderRespDTO notify = randomPojo(PayOrderRespDTO.class); - // mock 方法(channel) - PayChannelDO channel = randomPojo(PayChannelDO.class, o -> o.setId(10L)); - when(channelService.validPayChannel(eq(10L))).thenReturn(channel); - - // 调用 - orderService.notifyOrder(channelId, notify); - // 断言 - verify(payOrderServiceImpl).notifyOrder(same(channel), same(notify)); - } - } - - @Test - public void testNotifyOrderSuccess_orderExtension_notFound() { - // 准备参数 - PayChannelDO channel = randomPojo(PayChannelDO.class, o -> o.setId(10L)); - PayOrderRespDTO notify = randomPojo(PayOrderRespDTO.class, - o -> o.setStatus(PayOrderStatusEnum.SUCCESS.getStatus())); - - // 调用,并断言异常 - assertServiceException(() -> orderService.notifyOrder(channel, notify), - PAY_ORDER_EXTENSION_NOT_FOUND); - } - - @Test - public void testNotifyOrderSuccess_orderExtension_closed() { - // mock 数据(PayOrderExtensionDO) - PayOrderExtensionDO orderExtension = randomPojo(PayOrderExtensionDO.class, - o -> o.setStatus(PayOrderStatusEnum.CLOSED.getStatus()) - .setNo("P110")); - orderExtensionMapper.insert(orderExtension); - // 准备参数 - PayChannelDO channel = randomPojo(PayChannelDO.class, o -> o.setId(10L)); - PayOrderRespDTO notify = randomPojo(PayOrderRespDTO.class, - o -> o.setStatus(PayOrderStatusEnum.SUCCESS.getStatus()) - .setOutTradeNo("P110")); - - // 调用,并断言异常 - assertServiceException(() -> orderService.notifyOrder(channel, notify), - PAY_ORDER_EXTENSION_STATUS_IS_NOT_WAITING); - } - - @Test - public void testNotifyOrderSuccess_order_notFound() { - // mock 数据(PayOrderExtensionDO) - PayOrderExtensionDO orderExtension = randomPojo(PayOrderExtensionDO.class, - o -> o.setStatus(PayOrderStatusEnum.SUCCESS.getStatus()) - .setNo("P110")); - orderExtensionMapper.insert(orderExtension); - // 准备参数 - PayChannelDO channel = randomPojo(PayChannelDO.class, o -> o.setId(10L)); - PayOrderRespDTO notify = randomPojo(PayOrderRespDTO.class, - o -> o.setStatus(PayOrderStatusEnum.SUCCESS.getStatus()) - .setOutTradeNo("P110")); - - // 调用,并断言异常 - assertServiceException(() -> orderService.notifyOrder(channel, notify), - PAY_ORDER_NOT_FOUND); - // 断言 PayOrderExtensionDO :数据更新被回滚 - assertPojoEquals(orderExtension, orderExtensionMapper.selectOne(null)); - } - - @Test - public void testNotifyOrderSuccess_order_closed() { - testNotifyOrderSuccess_order_closedOrRefund(PayOrderStatusEnum.CLOSED.getStatus()); - } - - @Test - public void testNotifyOrderSuccess_order_refund() { - testNotifyOrderSuccess_order_closedOrRefund(PayOrderStatusEnum.REFUND.getStatus()); - } - - private void testNotifyOrderSuccess_order_closedOrRefund(Integer status) { - // mock 数据(PayOrderDO) - PayOrderDO order = randomPojo(PayOrderDO.class, o -> o.setStatus(status)); - orderMapper.insert(order); - // mock 数据(PayOrderExtensionDO) - PayOrderExtensionDO orderExtension = randomPojo(PayOrderExtensionDO.class, - o -> o.setStatus(PayOrderStatusEnum.SUCCESS.getStatus()) - .setNo("P110") - .setOrderId(order.getId())); - orderExtensionMapper.insert(orderExtension); - // 准备参数 - PayChannelDO channel = randomPojo(PayChannelDO.class, o -> o.setId(10L)); - PayOrderRespDTO notify = randomPojo(PayOrderRespDTO.class, - o -> o.setStatus(PayOrderStatusEnum.SUCCESS.getStatus()) - .setOutTradeNo("P110")); - - // 调用,并断言异常 - assertServiceException(() -> orderService.notifyOrder(channel, notify), - PAY_ORDER_STATUS_IS_NOT_WAITING); - // 断言 PayOrderExtensionDO :数据未更新,因为它是 SUCCESS - assertPojoEquals(orderExtension, orderExtensionMapper.selectOne(null)); - } - - @Test - public void testNotifyOrderSuccess_order_paid() { - // mock 数据(PayOrderDO) - PayOrderDO order = randomPojo(PayOrderDO.class, - o -> o.setStatus(PayOrderStatusEnum.SUCCESS.getStatus())); - orderMapper.insert(order); - // mock 数据(PayOrderExtensionDO) - PayOrderExtensionDO orderExtension = randomPojo(PayOrderExtensionDO.class, - o -> o.setStatus(PayOrderStatusEnum.SUCCESS.getStatus()) - .setNo("P110") - .setOrderId(order.getId())); - orderExtensionMapper.insert(orderExtension); - // 重要:需要将 order 的 extensionId 更新下 - order.setExtensionId(orderExtension.getId()); - orderMapper.updateById(order); - // 准备参数 - PayChannelDO channel = randomPojo(PayChannelDO.class, o -> o.setId(10L)); - PayOrderRespDTO notify = randomPojo(PayOrderRespDTO.class, - o -> o.setStatus(PayOrderStatusEnum.SUCCESS.getStatus()) - .setOutTradeNo("P110")); - - // 调用,并断言异常 - orderService.notifyOrder(channel, notify); - // 断言 PayOrderExtensionDO :数据未更新,因为它是 SUCCESS - assertPojoEquals(orderExtension, orderExtensionMapper.selectOne(null)); - // 断言 PayOrderDO :数据未更新,因为它是 SUCCESS - assertPojoEquals(order, orderMapper.selectOne(null)); - // 断言,调用 - verify(notifyService, never()).createPayNotifyTask(anyInt(), anyLong()); - } - - @Test - public void testNotifyOrderSuccess_order_waiting() { - // mock 数据(PayOrderDO) - PayOrderDO order = randomPojo(PayOrderDO.class, - o -> o.setStatus(PayOrderStatusEnum.WAITING.getStatus()) - .setPrice(10)); - orderMapper.insert(order); - // mock 数据(PayOrderExtensionDO) - PayOrderExtensionDO orderExtension = randomPojo(PayOrderExtensionDO.class, - o -> o.setStatus(PayOrderStatusEnum.WAITING.getStatus()) - .setNo("P110") - .setOrderId(order.getId())); - orderExtensionMapper.insert(orderExtension); - // 准备参数 - PayChannelDO channel = randomPojo(PayChannelDO.class, o -> o.setId(10L) - .setFeeRate(10D)); - PayOrderRespDTO notify = randomPojo(PayOrderRespDTO.class, - o -> o.setStatus(PayOrderStatusEnum.SUCCESS.getStatus()) - .setOutTradeNo("P110")); - - // 调用,并断言异常 - orderService.notifyOrder(channel, notify); - // 断言 PayOrderExtensionDO :数据未更新,因为它是 SUCCESS - orderExtension.setStatus(PayOrderStatusEnum.SUCCESS.getStatus()) - .setChannelNotifyData(toJsonString(notify)); - assertPojoEquals(orderExtension, orderExtensionMapper.selectOne(null), - "updateTime", "updater"); - // 断言 PayOrderDO :数据未更新,因为它是 SUCCESS - order.setStatus(PayOrderStatusEnum.SUCCESS.getStatus()) - .setChannelId(10L).setChannelCode(channel.getCode()) - .setSuccessTime(notify.getSuccessTime()).setExtensionId(orderExtension.getId()).setNo(orderExtension.getNo()) - .setChannelOrderNo(notify.getChannelOrderNo()).setChannelUserId(notify.getChannelUserId()) - .setChannelFeeRate(10D).setChannelFeePrice(1); - assertPojoEquals(order, orderMapper.selectOne(null), - "updateTime", "updater"); - // 断言,调用 - verify(notifyService).createPayNotifyTask(eq(PayNotifyTypeEnum.ORDER.getType()), - eq(orderExtension.getOrderId())); - } - - @Test - public void testNotifyOrderClosed_orderExtension_notFound() { - // 准备参数 - PayChannelDO channel = randomPojo(PayChannelDO.class, o -> o.setId(10L)); - PayOrderRespDTO notify = randomPojo(PayOrderRespDTO.class, - o -> o.setStatus(PayOrderStatusEnum.CLOSED.getStatus())); - - // 调用,并断言异常 - assertServiceException(() -> orderService.notifyOrder(channel, notify), - PAY_ORDER_EXTENSION_NOT_FOUND); - } - - @Test - public void testNotifyOrderClosed_orderExtension_closed() { - // mock 数据(PayOrderExtensionDO) - PayOrderExtensionDO orderExtension = randomPojo(PayOrderExtensionDO.class, - o -> o.setStatus(PayOrderStatusEnum.CLOSED.getStatus()) - .setNo("P110")); - orderExtensionMapper.insert(orderExtension); - // 准备参数 - PayChannelDO channel = randomPojo(PayChannelDO.class, o -> o.setId(10L)); - PayOrderRespDTO notify = randomPojo(PayOrderRespDTO.class, - o -> o.setStatus(PayOrderStatusEnum.CLOSED.getStatus()) - .setOutTradeNo("P110")); - - // 调用,并断言 - orderService.notifyOrder(channel, notify); - // 断言 PayOrderExtensionDO :数据未更新,因为它是 CLOSED - assertPojoEquals(orderExtension, orderExtensionMapper.selectOne(null)); - } - - @Test - public void testNotifyOrderClosed_orderExtension_paid() { - // mock 数据(PayOrderExtensionDO) - PayOrderExtensionDO orderExtension = randomPojo(PayOrderExtensionDO.class, - o -> o.setStatus(PayOrderStatusEnum.SUCCESS.getStatus()) - .setNo("P110")); - orderExtensionMapper.insert(orderExtension); - // 准备参数 - PayChannelDO channel = randomPojo(PayChannelDO.class, o -> o.setId(10L)); - PayOrderRespDTO notify = randomPojo(PayOrderRespDTO.class, - o -> o.setStatus(PayOrderStatusEnum.CLOSED.getStatus()) - .setOutTradeNo("P110")); - - // 调用,并断言 - orderService.notifyOrder(channel, notify); - // 断言 PayOrderExtensionDO :数据未更新,因为它是 SUCCESS - assertPojoEquals(orderExtension, orderExtensionMapper.selectOne(null)); - } - - @Test - public void testNotifyOrderClosed_orderExtension_refund() { - // mock 数据(PayOrderExtensionDO) - PayOrderExtensionDO orderExtension = randomPojo(PayOrderExtensionDO.class, - o -> o.setStatus(PayOrderStatusEnum.REFUND.getStatus()) - .setNo("P110")); - orderExtensionMapper.insert(orderExtension); - // 准备参数 - PayChannelDO channel = randomPojo(PayChannelDO.class, o -> o.setId(10L)); - PayOrderRespDTO notify = randomPojo(PayOrderRespDTO.class, - o -> o.setStatus(PayOrderStatusEnum.CLOSED.getStatus()) - .setOutTradeNo("P110")); - - // 调用,并断言异常 - assertServiceException(() -> orderService.notifyOrder(channel, notify), - PAY_ORDER_EXTENSION_STATUS_IS_NOT_WAITING); - } - - @Test - public void testNotifyOrderClosed_orderExtension_waiting() { - // mock 数据(PayOrderExtensionDO) - PayOrderExtensionDO orderExtension = randomPojo(PayOrderExtensionDO.class, - o -> o.setStatus(PayOrderStatusEnum.WAITING.getStatus()) - .setNo("P110")); - orderExtensionMapper.insert(orderExtension); - // 准备参数 - PayChannelDO channel = randomPojo(PayChannelDO.class, o -> o.setId(10L)); - PayOrderRespDTO notify = randomPojo(PayOrderRespDTO.class, - o -> o.setStatus(PayOrderStatusEnum.CLOSED.getStatus()) - .setOutTradeNo("P110")); - - // 调用 - orderService.notifyOrder(channel, notify); - // 断言 PayOrderExtensionDO - orderExtension.setStatus(PayOrderStatusEnum.CLOSED.getStatus()).setChannelNotifyData(toJsonString(notify)) - .setChannelErrorCode(notify.getChannelErrorCode()).setChannelErrorMsg(notify.getChannelErrorMsg()); - assertPojoEquals(orderExtension, orderExtensionMapper.selectOne(null), - "updateTime", "updater"); - } - - @Test - public void testUpdateOrderRefundPrice_notFound() { - // 准备参数 - Long id = randomLongId(); - Integer incrRefundPrice = randomInteger(); - - // 调用,并断言异常 - assertServiceException(() -> orderService.updateOrderRefundPrice(id, incrRefundPrice), - PAY_ORDER_NOT_FOUND); - } - - @Test - public void testUpdateOrderRefundPrice_waiting() { - testUpdateOrderRefundPrice_waitingOrClosed(PayOrderStatusEnum.WAITING.getStatus()); - } - - @Test - public void testUpdateOrderRefundPrice_closed() { - testUpdateOrderRefundPrice_waitingOrClosed(PayOrderStatusEnum.CLOSED.getStatus()); - } - - private void testUpdateOrderRefundPrice_waitingOrClosed(Integer status) { - // mock 数据(PayOrderDO) - PayOrderDO order = randomPojo(PayOrderDO.class, - o -> o.setStatus(status)); - orderMapper.insert(order); - // 准备参数 - Long id = order.getId(); - Integer incrRefundPrice = randomInteger(); - - // 调用,并断言异常 - assertServiceException(() -> orderService.updateOrderRefundPrice(id, incrRefundPrice), - PAY_ORDER_REFUND_FAIL_STATUS_ERROR); - } - - @Test - public void testUpdateOrderRefundPrice_priceExceed() { - // mock 数据(PayOrderDO) - PayOrderDO order = randomPojo(PayOrderDO.class, - o -> o.setStatus(PayOrderStatusEnum.SUCCESS.getStatus()) - .setRefundPrice(1).setPrice(10)); - orderMapper.insert(order); - // 准备参数 - Long id = order.getId(); - Integer incrRefundPrice = 10; - - // 调用,并断言异常 - assertServiceException(() -> orderService.updateOrderRefundPrice(id, incrRefundPrice), - REFUND_PRICE_EXCEED); - } - - @Test - public void testUpdateOrderRefundPrice_refund() { - testUpdateOrderRefundPrice_refundOrSuccess(PayOrderStatusEnum.REFUND.getStatus()); - } - - @Test - public void testUpdateOrderRefundPrice_success() { - testUpdateOrderRefundPrice_refundOrSuccess(PayOrderStatusEnum.SUCCESS.getStatus()); - } - - private void testUpdateOrderRefundPrice_refundOrSuccess(Integer status) { - // mock 数据(PayOrderDO) - PayOrderDO order = randomPojo(PayOrderDO.class, - o -> o.setStatus(status).setRefundPrice(1).setPrice(10)); - orderMapper.insert(order); - // 准备参数 - Long id = order.getId(); - Integer incrRefundPrice = 8; - - // 调用 - orderService.updateOrderRefundPrice(id, incrRefundPrice); - // 断言 - order.setRefundPrice(9).setStatus(PayOrderStatusEnum.REFUND.getStatus()); - assertPojoEquals(order, orderMapper.selectOne(null), - "updateTime", "updater"); - } - - @Test - public void testGetOrderExtension() { - // mock 数据(PayOrderExtensionDO) - PayOrderExtensionDO orderExtension = randomPojo(PayOrderExtensionDO.class); - orderExtensionMapper.insert(orderExtension); - // 准备参数 - Long id = orderExtension.getId(); - - // 调用 - PayOrderExtensionDO dbOrderExtension = orderService.getOrderExtension(id); - // 断言 - assertPojoEquals(dbOrderExtension, orderExtension); - } - - @Test - public void testSyncOrder_payClientNotFound() { - // 准备参数 - LocalDateTime minCreateTime = LocalDateTime.now().minus(Duration.ofMinutes(10)); - // mock 数据(PayOrderExtensionDO) - PayOrderExtensionDO orderExtension = randomPojo(PayOrderExtensionDO.class, - o -> o.setStatus(PayOrderStatusEnum.WAITING.getStatus()) - .setCreateTime(LocalDateTime.now())); - orderExtensionMapper.insert(orderExtension); - - // 调用 - int count = orderService.syncOrder(minCreateTime); - // 断言 - assertEquals(count, 0); - } - - @Test - public void testSyncOrder_exception() { - // 准备参数 - LocalDateTime minCreateTime = LocalDateTime.now().minus(Duration.ofMinutes(10)); - // mock 数据(PayOrderExtensionDO) - PayOrderExtensionDO orderExtension = randomPojo(PayOrderExtensionDO.class, - o -> o.setStatus(PayOrderStatusEnum.WAITING.getStatus()) - .setChannelId(10L) - .setCreateTime(LocalDateTime.now())); - orderExtensionMapper.insert(orderExtension); - // mock 方法(PayClient) - PayClient client = mock(PayClient.class); - when(channelService.getPayClient(eq(10L))).thenReturn(client); - // mock 方法(PayClient 异常) - when(client.getOrder(any())).thenThrow(new RuntimeException()); - - // 调用 - int count = orderService.syncOrder(minCreateTime); - // 断言 - assertEquals(count, 0); - } - - @Test - public void testSyncOrder_orderSuccess() { - PayOrderServiceImpl payOrderServiceImpl = mock(PayOrderServiceImpl.class); - try (MockedStatic springUtilMockedStatic = mockStatic(SpringUtil.class)) { - springUtilMockedStatic.when(() -> SpringUtil.getBean(eq(PayOrderServiceImpl.class))) - .thenReturn(payOrderServiceImpl); - - // 准备参数 - LocalDateTime minCreateTime = LocalDateTime.now().minus(Duration.ofMinutes(10)); - // mock 数据(PayOrderExtensionDO) - PayOrderExtensionDO orderExtension = randomPojo(PayOrderExtensionDO.class, - o -> o.setStatus(PayOrderStatusEnum.WAITING.getStatus()) - .setChannelId(10L).setNo("P110") - .setCreateTime(LocalDateTime.now())); - orderExtensionMapper.insert(orderExtension); - // mock 方法(PayClient) - PayClient client = mock(PayClient.class); - when(channelService.getPayClient(eq(10L))).thenReturn(client); - // mock 方法(PayClient 成功返回) - PayOrderRespDTO respDTO = randomPojo(PayOrderRespDTO.class, - o -> o.setStatus(PayOrderStatusEnum.SUCCESS.getStatus())); - when(client.getOrder(eq("P110"))).thenReturn(respDTO); - // mock 方法(PayChannelDO) - PayChannelDO channel = randomPojo(PayChannelDO.class, o -> o.setId(10L)); - when(channelService.validPayChannel(eq(10L))).thenReturn(channel); - - // 调用 - int count = orderService.syncOrder(minCreateTime); - // 断言 - assertEquals(count, 1); - verify(payOrderServiceImpl).notifyOrder(same(channel), same(respDTO)); - } - } - - @Test - public void testSyncOrder_orderClosed() { - PayOrderServiceImpl payOrderServiceImpl = mock(PayOrderServiceImpl.class); - try (MockedStatic springUtilMockedStatic = mockStatic(SpringUtil.class)) { - springUtilMockedStatic.when(() -> SpringUtil.getBean(eq(PayOrderServiceImpl.class))) - .thenReturn(payOrderServiceImpl); - - // 准备参数 - LocalDateTime minCreateTime = LocalDateTime.now().minus(Duration.ofMinutes(10)); - // mock 数据(PayOrderExtensionDO) - PayOrderExtensionDO orderExtension = randomPojo(PayOrderExtensionDO.class, - o -> o.setStatus(PayOrderStatusEnum.WAITING.getStatus()) - .setChannelId(10L).setNo("P110") - .setCreateTime(LocalDateTime.now())); - orderExtensionMapper.insert(orderExtension); - // mock 方法(PayClient) - PayClient client = mock(PayClient.class); - when(channelService.getPayClient(eq(10L))).thenReturn(client); - // mock 方法(PayClient 成功返回) - PayOrderRespDTO respDTO = randomPojo(PayOrderRespDTO.class, - o -> o.setStatus(PayOrderStatusEnum.CLOSED.getStatus())); - when(client.getOrder(eq("P110"))).thenReturn(respDTO); - // mock 方法(PayChannelDO) - PayChannelDO channel = randomPojo(PayChannelDO.class, o -> o.setId(10L)); - when(channelService.validPayChannel(eq(10L))).thenReturn(channel); - - // 调用 - int count = orderService.syncOrder(minCreateTime); - // 断言 - assertEquals(count, 0); - verify(payOrderServiceImpl, never()).notifyOrder(same(channel), same(respDTO)); - } - } - - @Test - public void testExpireOrder_orderExtension_isSuccess() { - // mock 数据(PayOrderDO) - PayOrderDO order = randomPojo(PayOrderDO.class, - o -> o.setStatus(PayOrderStatusEnum.WAITING.getStatus()) - .setExpireTime(addTime(Duration.ofMinutes(-1)))); - orderMapper.insert(order); - // mock 数据(PayOrderExtensionDO 已支付) - PayOrderExtensionDO orderExtension = randomPojo(PayOrderExtensionDO.class, - o -> o.setStatus(PayOrderStatusEnum.SUCCESS.getStatus()) - .setOrderId(order.getId())); - orderExtensionMapper.insert(orderExtension); - // mock 方法(PayClient) - PayClient client = mock(PayClient.class); - when(channelService.getPayClient(eq(10L))).thenReturn(client); - - // 调用 - int count = orderService.expireOrder(); - // 断言 - assertEquals(count, 0); - // 断言 order 没有变化,因为没更新 - assertPojoEquals(order, orderMapper.selectOne(null)); - } - - @Test - public void testExpireOrder_payClient_notFound() { - // mock 数据(PayOrderDO) - PayOrderDO order = randomPojo(PayOrderDO.class, - o -> o.setStatus(PayOrderStatusEnum.WAITING.getStatus()) - .setExpireTime(addTime(Duration.ofMinutes(-1)))); - orderMapper.insert(order); - // mock 数据(PayOrderExtensionDO 等待中) - PayOrderExtensionDO orderExtension = randomPojo(PayOrderExtensionDO.class, - o -> o.setStatus(PayOrderStatusEnum.WAITING.getStatus()) - .setOrderId(order.getId()) - .setChannelId(10L)); - orderExtensionMapper.insert(orderExtension); - - // 调用 - int count = orderService.expireOrder(); - // 断言 - assertEquals(count, 0); - // 断言 order 没有变化,因为没更新 - assertPojoEquals(order, orderMapper.selectOne(null)); - } - - @Test - public void testExpireOrder_getOrder_isRefund() { - // mock 数据(PayOrderDO) - PayOrderDO order = randomPojo(PayOrderDO.class, - o -> o.setStatus(PayOrderStatusEnum.WAITING.getStatus()) - .setExpireTime(addTime(Duration.ofMinutes(-1)))); - orderMapper.insert(order); - // mock 数据(PayOrderExtensionDO 等待中) - PayOrderExtensionDO orderExtension = randomPojo(PayOrderExtensionDO.class, - o -> o.setStatus(PayOrderStatusEnum.WAITING.getStatus()) - .setOrderId(order.getId()).setNo("P110") - .setChannelId(10L)); - orderExtensionMapper.insert(orderExtension); - // mock 方法(PayClient) - PayClient client = mock(PayClient.class); - when(channelService.getPayClient(eq(10L))).thenReturn(client); - // mock 方法(PayClient 退款返回) - PayOrderRespDTO respDTO = randomPojo(PayOrderRespDTO.class, - o -> o.setStatus(PayOrderStatusEnum.REFUND.getStatus())); - when(client.getOrder(eq("P110"))).thenReturn(respDTO); - - // 调用 - int count = orderService.expireOrder(); - // 断言 - assertEquals(count, 0); - // 断言 order 没有变化,因为没更新 - assertPojoEquals(order, orderMapper.selectOne(null)); - } - - @Test - public void testExpireOrder_getOrder_isSuccess() { - PayOrderServiceImpl payOrderServiceImpl = mock(PayOrderServiceImpl.class); - try (MockedStatic springUtilMockedStatic = mockStatic(SpringUtil.class)) { - springUtilMockedStatic.when(() -> SpringUtil.getBean(eq(PayOrderServiceImpl.class))) - .thenReturn(payOrderServiceImpl); - - // mock 数据(PayOrderDO) - PayOrderDO order = randomPojo(PayOrderDO.class, - o -> o.setStatus(PayOrderStatusEnum.WAITING.getStatus()) - .setExpireTime(addTime(Duration.ofMinutes(-1)))); - orderMapper.insert(order); - // mock 数据(PayOrderExtensionDO 等待中) - PayOrderExtensionDO orderExtension = randomPojo(PayOrderExtensionDO.class, - o -> o.setStatus(PayOrderStatusEnum.WAITING.getStatus()) - .setOrderId(order.getId()).setNo("P110") - .setChannelId(10L)); - orderExtensionMapper.insert(orderExtension); - // mock 方法(PayClient) - PayClient client = mock(PayClient.class); - when(channelService.getPayClient(eq(10L))).thenReturn(client); - // mock 方法(PayClient 成功返回) - PayOrderRespDTO respDTO = randomPojo(PayOrderRespDTO.class, - o -> o.setStatus(PayOrderStatusEnum.SUCCESS.getStatus())); - when(client.getOrder(eq("P110"))).thenReturn(respDTO); - // mock 方法(PayChannelDO) - PayChannelDO channel = randomPojo(PayChannelDO.class, o -> o.setId(10L)); - when(channelService.validPayChannel(eq(10L))).thenReturn(channel); - - // 调用 - int count = orderService.expireOrder(); - // 断言 - assertEquals(count, 0); - // 断言 order 没有变化,因为没更新 - assertPojoEquals(order, orderMapper.selectOne(null)); - verify(payOrderServiceImpl).notifyOrder(same(channel), same(respDTO)); - } - } - - @Test - public void testExpireOrder_success() { - // mock 数据(PayOrderDO) - PayOrderDO order = randomPojo(PayOrderDO.class, - o -> o.setStatus(PayOrderStatusEnum.WAITING.getStatus()) - .setExpireTime(addTime(Duration.ofMinutes(-1)))); - orderMapper.insert(order); - // mock 数据(PayOrderExtensionDO 等待中) - PayOrderExtensionDO orderExtension = randomPojo(PayOrderExtensionDO.class, - o -> o.setStatus(PayOrderStatusEnum.WAITING.getStatus()) - .setOrderId(order.getId()).setNo("P110") - .setChannelId(10L)); - orderExtensionMapper.insert(orderExtension); - // mock 方法(PayClient) - PayClient client = mock(PayClient.class); - when(channelService.getPayClient(eq(10L))).thenReturn(client); - // mock 方法(PayClient 关闭返回) - PayOrderRespDTO respDTO = randomPojo(PayOrderRespDTO.class, - o -> o.setStatus(PayOrderStatusEnum.CLOSED.getStatus())); - when(client.getOrder(eq("P110"))).thenReturn(respDTO); - - // 调用 - int count = orderService.expireOrder(); - // 断言 - assertEquals(count, 1); - // 断言 extension 变化 - orderExtension.setStatus(PayOrderStatusEnum.CLOSED.getStatus()) - .setChannelNotifyData(toJsonString(respDTO)); - assertPojoEquals(orderExtension, orderExtensionMapper.selectOne(null), - "updateTime", "updater"); - // 断言 order 变化 - order.setStatus(PayOrderStatusEnum.CLOSED.getStatus()); - assertPojoEquals(order, orderMapper.selectOne(null), - "updateTime", "updater"); - } - -} 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 deleted file mode 100755 index 87c4cdb..0000000 --- a/yudao-module-pay/yudao-module-pay-biz/src/test/java/cn/iocoder/yudao/module/pay/service/refund/PayRefundServiceTest.java +++ /dev/null @@ -1,699 +0,0 @@ -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.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; -import cn.iocoder.yudao.module.pay.controller.admin.refund.vo.PayRefundPageReqVO; -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; -import cn.iocoder.yudao.module.pay.dal.dataobject.refund.PayRefundDO; -import cn.iocoder.yudao.module.pay.dal.mysql.refund.PayRefundMapper; -import cn.iocoder.yudao.module.pay.dal.redis.no.PayNoRedisDAO; -import cn.iocoder.yudao.module.pay.enums.notify.PayNotifyTypeEnum; -import cn.iocoder.yudao.module.pay.enums.order.PayOrderStatusEnum; -import cn.iocoder.yudao.module.pay.enums.refund.PayRefundStatusEnum; -import cn.iocoder.yudao.module.pay.framework.pay.config.PayProperties; -import cn.iocoder.yudao.module.pay.service.app.PayAppService; -import cn.iocoder.yudao.module.pay.service.channel.PayChannelService; -import cn.iocoder.yudao.module.pay.service.notify.PayNotifyService; -import cn.iocoder.yudao.module.pay.service.order.PayOrderService; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.mockito.MockedStatic; -import org.springframework.boot.test.mock.mockito.MockBean; -import org.springframework.context.annotation.Import; - -import jakarta.annotation.Resource; -import java.util.List; - -import static cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.buildBetweenTime; -import static cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.buildTime; -import static cn.iocoder.yudao.framework.common.util.json.JsonUtils.toJsonString; -import static cn.iocoder.yudao.framework.common.util.object.ObjectUtils.cloneIgnoreId; -import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertPojoEquals; -import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertServiceException; -import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo; -import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomString; -import static cn.iocoder.yudao.module.pay.enums.ErrorCodeConstants.*; -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.*; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.*; - -/** - * {@link PayRefundServiceImpl} 的单元测试类 - * - * @author 芋艿 - */ -@Import({PayRefundServiceImpl.class, PayNoRedisDAO.class}) -public class PayRefundServiceTest extends BaseDbAndRedisUnitTest { - - @Resource - private PayRefundServiceImpl refundService; - - @Resource - private PayRefundMapper refundMapper; - - @MockBean - private PayProperties payProperties; - @MockBean - private PayOrderService orderService; - @MockBean - private PayAppService appService; - @MockBean - private PayChannelService channelService; - @MockBean - private PayNotifyService notifyService; - - @BeforeEach - public void setUp() { - when(payProperties.getRefundNotifyUrl()).thenReturn("http://127.0.0.1"); - } - - @Test - public void testGetRefund() { - // mock 数据 - PayRefundDO refund = randomPojo(PayRefundDO.class); - refundMapper.insert(refund); - // 准备参数 - Long id = refund.getId(); - - // 调用 - PayRefundDO dbRefund = refundService.getRefund(id); - // 断言 - assertPojoEquals(dbRefund, refund); - } - - @Test - public void testGetRefundCountByAppId() { - // mock 数据 - PayRefundDO refund01 = randomPojo(PayRefundDO.class); - refundMapper.insert(refund01); - PayRefundDO refund02 = randomPojo(PayRefundDO.class); - refundMapper.insert(refund02); - // 准备参数 - Long appId = refund01.getAppId(); - - // 调用 - Long count = refundService.getRefundCountByAppId(appId); - // 断言 - assertEquals(count, 1); - } - - @Test - public void testGetRefundPage() { - // mock 数据 - PayRefundDO dbRefund = randomPojo(PayRefundDO.class, o -> { // 等会查询到 - o.setAppId(1L); - o.setChannelCode(PayChannelEnum.WX_PUB.getCode()); - o.setMerchantOrderId("MOT0000001"); - o.setMerchantRefundId("MRF0000001"); - o.setStatus(PayOrderStatusEnum.SUCCESS.getStatus()); - o.setChannelOrderNo("CH0000001"); - o.setChannelRefundNo("CHR0000001"); - o.setCreateTime(buildTime(2021, 1, 10)); - }); - refundMapper.insert(dbRefund); - // 测试 appId 不匹配 - refundMapper.insert(cloneIgnoreId(dbRefund, o -> o.setAppId(2L))); - // 测试 channelCode 不匹配 - refundMapper.insert(cloneIgnoreId(dbRefund, o -> o.setChannelCode(PayChannelEnum.ALIPAY_APP.getCode()))); - // 测试 merchantOrderId 不匹配 - refundMapper.insert(cloneIgnoreId(dbRefund, o -> o.setMerchantOrderId(randomString()))); - // 测试 merchantRefundId 不匹配 - refundMapper.insert(cloneIgnoreId(dbRefund, o -> o.setMerchantRefundId(randomString()))); - // 测试 channelOrderNo 不匹配 - refundMapper.insert(cloneIgnoreId(dbRefund, o -> o.setChannelOrderNo(randomString()))); - // 测试 channelRefundNo 不匹配 - refundMapper.insert(cloneIgnoreId(dbRefund, o -> o.setChannelRefundNo(randomString()))); - // 测试 status 不匹配 - refundMapper.insert(cloneIgnoreId(dbRefund, o -> o.setStatus(PayOrderStatusEnum.WAITING.getStatus()))); - // 测试 createTime 不匹配 - refundMapper.insert(cloneIgnoreId(dbRefund, o -> o.setCreateTime(buildTime(2021, 1, 1)))); - // 准备参数 - PayRefundPageReqVO reqVO = new PayRefundPageReqVO(); - reqVO.setAppId(1L); - reqVO.setChannelCode(PayChannelEnum.WX_PUB.getCode()); - reqVO.setMerchantOrderId("MOT0000001"); - reqVO.setMerchantRefundId("MRF0000001"); - reqVO.setStatus(PayOrderStatusEnum.SUCCESS.getStatus()); - reqVO.setChannelOrderNo("CH0000001"); - reqVO.setChannelRefundNo("CHR0000001"); - reqVO.setCreateTime(buildBetweenTime(2021, 1, 9, 2021, 1, 11)); - - // 调用 - PageResult pageResult = refundService.getRefundPage(reqVO); - // 断言 - assertEquals(1, pageResult.getTotal()); - assertEquals(1, pageResult.getList().size()); - assertPojoEquals(dbRefund, pageResult.getList().get(0)); - } - - @Test - public void testGetRefundList() { - // mock 数据 - PayRefundDO dbRefund = randomPojo(PayRefundDO.class, o -> { // 等会查询到 - o.setAppId(1L); - o.setChannelCode(PayChannelEnum.WX_PUB.getCode()); - o.setMerchantOrderId("MOT0000001"); - o.setMerchantRefundId("MRF0000001"); - o.setStatus(PayOrderStatusEnum.SUCCESS.getStatus()); - o.setChannelOrderNo("CH0000001"); - o.setChannelRefundNo("CHR0000001"); - o.setCreateTime(buildTime(2021, 1, 10)); - }); - refundMapper.insert(dbRefund); - // 测试 appId 不匹配 - refundMapper.insert(cloneIgnoreId(dbRefund, o -> o.setAppId(2L))); - // 测试 channelCode 不匹配 - refundMapper.insert(cloneIgnoreId(dbRefund, o -> o.setChannelCode(PayChannelEnum.ALIPAY_APP.getCode()))); - // 测试 merchantOrderId 不匹配 - refundMapper.insert(cloneIgnoreId(dbRefund, o -> o.setMerchantOrderId(randomString()))); - // 测试 merchantRefundId 不匹配 - refundMapper.insert(cloneIgnoreId(dbRefund, o -> o.setMerchantRefundId(randomString()))); - // 测试 channelOrderNo 不匹配 - refundMapper.insert(cloneIgnoreId(dbRefund, o -> o.setChannelOrderNo(randomString()))); - // 测试 channelRefundNo 不匹配 - refundMapper.insert(cloneIgnoreId(dbRefund, o -> o.setChannelRefundNo(randomString()))); - // 测试 status 不匹配 - refundMapper.insert(cloneIgnoreId(dbRefund, o -> o.setStatus(PayOrderStatusEnum.WAITING.getStatus()))); - // 测试 createTime 不匹配 - refundMapper.insert(cloneIgnoreId(dbRefund, o -> o.setCreateTime(buildTime(2021, 1, 1)))); - // 准备参数 - PayRefundExportReqVO reqVO = new PayRefundExportReqVO(); - reqVO.setAppId(1L); - reqVO.setChannelCode(PayChannelEnum.WX_PUB.getCode()); - reqVO.setMerchantOrderId("MOT0000001"); - reqVO.setMerchantRefundId("MRF0000001"); - reqVO.setStatus(PayOrderStatusEnum.SUCCESS.getStatus()); - reqVO.setChannelOrderNo("CH0000001"); - reqVO.setChannelRefundNo("CHR0000001"); - reqVO.setCreateTime(buildBetweenTime(2021, 1, 9, 2021, 1, 11)); - - // 调用 - List list = refundService.getRefundList(reqVO); - // 断言 - assertEquals(1, list.size()); - assertPojoEquals(dbRefund, list.get(0)); - } - - @Test - public void testCreateRefund_orderNotFound() { - PayRefundCreateReqDTO reqDTO = randomPojo(PayRefundCreateReqDTO.class, - o -> o.setAppKey("demo")); - // mock 方法(app) - PayAppDO app = randomPojo(PayAppDO.class, o -> o.setId(1L)); - when(appService.validPayApp(eq("demo"))).thenReturn(app); - - // 调用,并断言异常 - assertServiceException(() -> refundService.createRefund(reqDTO), - PAY_ORDER_NOT_FOUND); - } - - @Test - public void testCreateRefund_orderWaiting() { - testCreateRefund_orderWaitingOrClosed(PayOrderStatusEnum.WAITING.getStatus()); - } - - @Test - public void testCreateRefund_orderClosed() { - testCreateRefund_orderWaitingOrClosed(PayOrderStatusEnum.CLOSED.getStatus()); - } - - private void testCreateRefund_orderWaitingOrClosed(Integer status) { - // 准备参数 - PayRefundCreateReqDTO reqDTO = randomPojo(PayRefundCreateReqDTO.class, - o -> o.setAppKey("demo").setMerchantOrderId("100")); - // mock 方法(app) - PayAppDO app = randomPojo(PayAppDO.class, o -> o.setId(1L)); - when(appService.validPayApp(eq("demo"))).thenReturn(app); - // mock 数据(order) - PayOrderDO order = randomPojo(PayOrderDO.class, o -> o.setStatus(status)); - when(orderService.getOrder(eq(1L), eq("100"))).thenReturn(order); - - // 调用,并断言异常 - assertServiceException(() -> refundService.createRefund(reqDTO), - PAY_ORDER_REFUND_FAIL_STATUS_ERROR); - } - - @Test - public void testCreateRefund_refundPriceExceed() { - // 准备参数 - PayRefundCreateReqDTO reqDTO = randomPojo(PayRefundCreateReqDTO.class, - o -> o.setAppKey("demo").setMerchantOrderId("100").setPrice(10)); - // mock 方法(app) - PayAppDO app = randomPojo(PayAppDO.class, o -> o.setId(1L)); - when(appService.validPayApp(eq("demo"))).thenReturn(app); - // mock 数据(order) - PayOrderDO order = randomPojo(PayOrderDO.class, o -> - o.setStatus(PayOrderStatusEnum.REFUND.getStatus()) - .setPrice(10).setRefundPrice(1)); - when(orderService.getOrder(eq(1L), eq("100"))).thenReturn(order); - - // 调用,并断言异常 - assertServiceException(() -> refundService.createRefund(reqDTO), - REFUND_PRICE_EXCEED); - } - - @Test - public void testCreateRefund_orderHasRefunding() { - // 准备参数 - PayRefundCreateReqDTO reqDTO = randomPojo(PayRefundCreateReqDTO.class, - o -> o.setAppKey("demo").setMerchantOrderId("100").setPrice(10)); - // mock 方法(app) - PayAppDO app = randomPojo(PayAppDO.class, o -> o.setId(1L)); - when(appService.validPayApp(eq("demo"))).thenReturn(app); - // mock 数据(order) - PayOrderDO order = randomPojo(PayOrderDO.class, o -> - o.setStatus(PayOrderStatusEnum.REFUND.getStatus()) - .setPrice(10).setRefundPrice(1)); - when(orderService.getOrder(eq(1L), eq("100"))).thenReturn(order); - // mock 数据(refund 在退款中) - PayRefundDO refund = randomPojo(PayRefundDO.class, o -> - o.setOrderId(order.getId()).setStatus(PayOrderStatusEnum.WAITING.getStatus())); - refundMapper.insert(refund); - - // 调用,并断言异常 - assertServiceException(() -> refundService.createRefund(reqDTO), - REFUND_PRICE_EXCEED); - } - - @Test - public void testCreateRefund_channelNotFound() { - // 准备参数 - PayRefundCreateReqDTO reqDTO = randomPojo(PayRefundCreateReqDTO.class, - o -> o.setAppKey("demo").setMerchantOrderId("100").setPrice(9)); - // mock 方法(app) - PayAppDO app = randomPojo(PayAppDO.class, o -> o.setId(1L)); - when(appService.validPayApp(eq("demo"))).thenReturn(app); - // mock 数据(order) - PayOrderDO order = randomPojo(PayOrderDO.class, o -> - o.setStatus(PayOrderStatusEnum.REFUND.getStatus()) - .setPrice(10).setRefundPrice(1) - .setChannelId(1L).setChannelCode(PayChannelEnum.ALIPAY_APP.getCode())); - when(orderService.getOrder(eq(1L), eq("100"))).thenReturn(order); - // mock 方法(channel) - PayChannelDO channel = randomPojo(PayChannelDO.class, o -> o.setId(10L) - .setCode(PayChannelEnum.ALIPAY_APP.getCode())); - when(channelService.validPayChannel(eq(1L))).thenReturn(channel); - - // 调用,并断言异常 - assertServiceException(() -> refundService.createRefund(reqDTO), - CHANNEL_NOT_FOUND); - } - - @Test - public void testCreateRefund_refundExists() { - // 准备参数 - PayRefundCreateReqDTO reqDTO = randomPojo(PayRefundCreateReqDTO.class, - o -> o.setAppKey("demo").setMerchantOrderId("100").setPrice(9) - .setMerchantRefundId("200").setReason("测试退款")); - // mock 方法(app) - PayAppDO app = randomPojo(PayAppDO.class, o -> o.setId(1L)); - when(appService.validPayApp(eq("demo"))).thenReturn(app); - // mock 数据(order) - PayOrderDO order = randomPojo(PayOrderDO.class, o -> - o.setStatus(PayOrderStatusEnum.REFUND.getStatus()) - .setPrice(10).setRefundPrice(1) - .setChannelId(1L).setChannelCode(PayChannelEnum.ALIPAY_APP.getCode())); - when(orderService.getOrder(eq(1L), eq("100"))).thenReturn(order); - // mock 方法(channel) - PayChannelDO channel = randomPojo(PayChannelDO.class, o -> o.setId(10L) - .setCode(PayChannelEnum.ALIPAY_APP.getCode())); - when(channelService.validPayChannel(eq(1L))).thenReturn(channel); - // mock 方法(client) - PayClient client = mock(PayClient.class); - when(channelService.getPayClient(eq(10L))).thenReturn(client); - // mock 数据(refund 已存在) - PayRefundDO refund = randomPojo(PayRefundDO.class, o -> - o.setAppId(1L).setMerchantRefundId("200")); - refundMapper.insert(refund); - - // 调用,并断言异常 - assertServiceException(() -> refundService.createRefund(reqDTO), - REFUND_EXISTS); - } - - @Test - public void testCreateRefund_invokeException() { - // 准备参数 - PayRefundCreateReqDTO reqDTO = randomPojo(PayRefundCreateReqDTO.class, - o -> o.setAppKey("demo").setMerchantOrderId("100").setPrice(9) - .setMerchantRefundId("200").setReason("测试退款")); - // mock 方法(app) - PayAppDO app = randomPojo(PayAppDO.class, o -> o.setId(1L)); - when(appService.validPayApp(eq("demo"))).thenReturn(app); - // mock 数据(order) - PayOrderDO order = randomPojo(PayOrderDO.class, o -> - o.setStatus(PayOrderStatusEnum.REFUND.getStatus()) - .setPrice(10).setRefundPrice(1) - .setChannelId(10L).setChannelCode(PayChannelEnum.ALIPAY_APP.getCode())); - when(orderService.getOrder(eq(1L), eq("100"))).thenReturn(order); - // mock 方法(channel) - PayChannelDO channel = randomPojo(PayChannelDO.class, o -> o.setId(10L) - .setCode(PayChannelEnum.ALIPAY_APP.getCode())); - when(channelService.validPayChannel(eq(10L))).thenReturn(channel); - // mock 方法(client) - PayClient client = mock(PayClient.class); - when(channelService.getPayClient(eq(10L))).thenReturn(client); - // mock 方法(client 调用发生异常) - when(client.unifiedRefund(any(PayRefundUnifiedReqDTO.class))).thenThrow(new RuntimeException()); - - // 调用 - Long refundId = refundService.createRefund(reqDTO); - // 断言 - PayRefundDO refundDO = refundMapper.selectById(refundId); - assertPojoEquals(reqDTO, refundDO); - assertNotNull(refundDO.getNo()); - assertThat(refundDO) - .extracting("orderId", "orderNo", "channelId", "channelCode", - "notifyUrl", "channelOrderNo", "status", "payPrice", "refundPrice") - .containsExactly(order.getId(), order.getNo(), channel.getId(), channel.getCode(), - app.getRefundNotifyUrl(), order.getChannelOrderNo(), PayRefundStatusEnum.WAITING.getStatus(), - order.getPrice(), reqDTO.getPrice()); - } - - @Test - public void testCreateRefund_invokeSuccess() { - PayRefundServiceImpl payRefundServiceImpl = mock(PayRefundServiceImpl.class); - try (MockedStatic springUtilMockedStatic = mockStatic(SpringUtil.class)) { - springUtilMockedStatic.when(() -> SpringUtil.getBean(eq(PayRefundServiceImpl.class))) - .thenReturn(payRefundServiceImpl); - - // 准备参数 - PayRefundCreateReqDTO reqDTO = randomPojo(PayRefundCreateReqDTO.class, - o -> o.setAppKey("demo").setMerchantOrderId("100").setPrice(9) - .setMerchantRefundId("200").setReason("测试退款")); - // mock 方法(app) - PayAppDO app = randomPojo(PayAppDO.class, o -> o.setId(1L)); - when(appService.validPayApp(eq("demo"))).thenReturn(app); - // mock 数据(order) - PayOrderDO order = randomPojo(PayOrderDO.class, o -> - o.setStatus(PayOrderStatusEnum.REFUND.getStatus()) - .setPrice(10).setRefundPrice(1) - .setChannelId(10L).setChannelCode(PayChannelEnum.ALIPAY_APP.getCode())); - when(orderService.getOrder(eq(1L), eq("100"))).thenReturn(order); - // mock 方法(channel) - PayChannelDO channel = randomPojo(PayChannelDO.class, o -> o.setId(10L) - .setCode(PayChannelEnum.ALIPAY_APP.getCode())); - when(channelService.validPayChannel(eq(10L))).thenReturn(channel); - // mock 方法(client) - PayClient client = mock(PayClient.class); - when(channelService.getPayClient(eq(10L))).thenReturn(client); - // mock 方法(client 成功) - PayRefundRespDTO refundRespDTO = randomPojo(PayRefundRespDTO.class); - when(client.unifiedRefund(argThat(unifiedReqDTO -> { - assertNotNull(unifiedReqDTO.getOutRefundNo()); - assertThat(unifiedReqDTO) - .extracting("payPrice", "refundPrice", "outTradeNo", - "notifyUrl", "reason") - .containsExactly(order.getPrice(), reqDTO.getPrice(), order.getNo(), - "http://127.0.0.1/10", reqDTO.getReason()); - return true; - }))).thenReturn(refundRespDTO); - - // 调用 - Long refundId = refundService.createRefund(reqDTO); - // 断言 - PayRefundDO refundDO = refundMapper.selectById(refundId); - assertPojoEquals(reqDTO, refundDO); - assertNotNull(refundDO.getNo()); - assertThat(refundDO) - .extracting("orderId", "orderNo", "channelId", "channelCode", - "notifyUrl", "channelOrderNo", "status", "payPrice", "refundPrice") - .containsExactly(order.getId(), order.getNo(), channel.getId(), channel.getCode(), - app.getRefundNotifyUrl(), order.getChannelOrderNo(), PayRefundStatusEnum.WAITING.getStatus(), - order.getPrice(), reqDTO.getPrice()); - // 断言调用 - verify(payRefundServiceImpl).notifyRefund(same(channel), same(refundRespDTO)); - } - } - - @Test - public void testNotifyRefund() { - PayRefundServiceImpl payRefundServiceImpl = mock(PayRefundServiceImpl.class); - try (MockedStatic springUtilMockedStatic = mockStatic(SpringUtil.class)) { - springUtilMockedStatic.when(() -> SpringUtil.getBean(eq(PayRefundServiceImpl.class))) - .thenReturn(payRefundServiceImpl); - - // 准备参数 - Long channelId = 10L; - PayRefundRespDTO refundRespDTO = randomPojo(PayRefundRespDTO.class); - // mock 方法(channel) - PayChannelDO channel = randomPojo(PayChannelDO.class, o -> o.setId(10L)); - when(channelService.validPayChannel(eq(10L))).thenReturn(channel); - - // 调用 - refundService.notifyRefund(channelId, refundRespDTO); - // 断言 - verify(payRefundServiceImpl).notifyRefund(same(channel), same(refundRespDTO)); - } - } - - @Test - public void testNotifyRefundSuccess_notFound() { - // 准备参数 - PayChannelDO channel = randomPojo(PayChannelDO.class, o -> o.setId(10L).setAppId(1L)); - PayRefundRespDTO refundRespDTO = randomPojo(PayRefundRespDTO.class, - o -> o.setStatus(PayRefundStatusEnum.SUCCESS.getStatus()).setOutRefundNo("R100")); - - // 调用,并断言异常 - assertServiceException(() -> refundService.notifyRefund(channel, refundRespDTO), - REFUND_NOT_FOUND); - } - - @Test - public void testNotifyRefundSuccess_isSuccess() { - // 准备参数 - PayChannelDO channel = randomPojo(PayChannelDO.class, o -> o.setId(10L).setAppId(1L)); - PayRefundRespDTO refundRespDTO = randomPojo(PayRefundRespDTO.class, - 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())); - refundMapper.insert(refund); - - // 调用 - refundService.notifyRefund(channel, refundRespDTO); - // 断言,refund 没有更新,因为已经退款成功 - assertPojoEquals(refund, refundMapper.selectById(refund.getId())); - } - - @Test - public void testNotifyRefundSuccess_failure() { - // 准备参数 - PayChannelDO channel = randomPojo(PayChannelDO.class, o -> o.setId(10L).setAppId(1L)); - PayRefundRespDTO refundRespDTO = randomPojo(PayRefundRespDTO.class, - 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())); - refundMapper.insert(refund); - - // 调用,并断言异常 - assertServiceException(() -> refundService.notifyRefund(channel, refundRespDTO), - REFUND_STATUS_IS_NOT_WAITING); - } - - @Test - public void testNotifyRefundSuccess_updateOrderException() { - // 准备参数 - PayChannelDO channel = randomPojo(PayChannelDO.class, o -> o.setId(10L).setAppId(1L)); - PayRefundRespDTO refundRespDTO = randomPojo(PayRefundRespDTO.class, - 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()) - .setOrderId(100L).setRefundPrice(23)); - refundMapper.insert(refund); - // mock 方法(order + 更新异常) - doThrow(new RuntimeException()).when(orderService) - .updateOrderRefundPrice(eq(100L), eq(23)); - - // 调用,并断言异常 - assertThrows(RuntimeException.class, () -> refundService.notifyRefund(channel, refundRespDTO)); - // 断言,refund 没有更新,因为事务回滚了 - assertPojoEquals(refund, refundMapper.selectById(refund.getId())); - } - - @Test - public void testNotifyRefundSuccess_success() { - // 准备参数 - PayChannelDO channel = randomPojo(PayChannelDO.class, o -> o.setId(10L).setAppId(1L)); - PayRefundRespDTO refundRespDTO = randomPojo(PayRefundRespDTO.class, - 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()) - .setOrderId(100L).setRefundPrice(23)); - refundMapper.insert(refund); - - // 调用 - refundService.notifyRefund(channel, refundRespDTO); - // 断言,refund - refund.setSuccessTime(refundRespDTO.getSuccessTime()) - .setChannelRefundNo(refundRespDTO.getChannelRefundNo()) - .setStatus(PayRefundStatusEnum.SUCCESS.getStatus()) - .setChannelNotifyData(toJsonString(refundRespDTO)); - assertPojoEquals(refund, refundMapper.selectById(refund.getId()), - "updateTime", "updater"); - // 断言,调用 - verify(orderService).updateOrderRefundPrice(eq(100L), eq(23)); - verify(notifyService).createPayNotifyTask(eq(PayNotifyTypeEnum.REFUND.getType()), - eq(refund.getId())); - } - - @Test - public void testNotifyRefundFailure_notFound() { - // 准备参数 - PayChannelDO channel = randomPojo(PayChannelDO.class, o -> o.setId(10L).setAppId(1L)); - PayRefundRespDTO refundRespDTO = randomPojo(PayRefundRespDTO.class, - o -> o.setStatus(PayRefundStatusEnum.FAILURE.getStatus()).setOutRefundNo("R100")); - - // 调用,并断言异常 - assertServiceException(() -> refundService.notifyRefund(channel, refundRespDTO), - REFUND_NOT_FOUND); - } - - @Test - public void testNotifyRefundFailure_isFailure() { - // 准备参数 - PayChannelDO channel = randomPojo(PayChannelDO.class, o -> o.setId(10L).setAppId(1L)); - PayRefundRespDTO refundRespDTO = randomPojo(PayRefundRespDTO.class, - 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())); - refundMapper.insert(refund); - - // 调用 - refundService.notifyRefund(channel, refundRespDTO); - // 断言,refund 没有更新,因为已经退款失败 - assertPojoEquals(refund, refundMapper.selectById(refund.getId())); - } - - @Test - public void testNotifyRefundFailure_isSuccess() { - // 准备参数 - PayChannelDO channel = randomPojo(PayChannelDO.class, o -> o.setId(10L).setAppId(1L)); - PayRefundRespDTO refundRespDTO = randomPojo(PayRefundRespDTO.class, - 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())); - refundMapper.insert(refund); - - // 调用,并断言异常 - assertServiceException(() -> refundService.notifyRefund(channel, refundRespDTO), - REFUND_STATUS_IS_NOT_WAITING); - } - - @Test - public void testNotifyRefundFailure_success() { - // 准备参数 - PayChannelDO channel = randomPojo(PayChannelDO.class, o -> o.setId(10L).setAppId(1L)); - PayRefundRespDTO refundRespDTO = randomPojo(PayRefundRespDTO.class, - 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()) - .setOrderId(100L).setRefundPrice(23)); - refundMapper.insert(refund); - - // 调用 - refundService.notifyRefund(channel, refundRespDTO); - // 断言,refund - refund.setChannelRefundNo(refundRespDTO.getChannelRefundNo()) - .setStatus(PayRefundStatusEnum.FAILURE.getStatus()) - .setChannelNotifyData(toJsonString(refundRespDTO)) - .setChannelErrorCode(refundRespDTO.getChannelErrorCode()) - .setChannelErrorMsg(refundRespDTO.getChannelErrorMsg()); - assertPojoEquals(refund, refundMapper.selectById(refund.getId()), - "updateTime", "updater"); - // 断言,调用 - verify(notifyService).createPayNotifyTask(eq(PayNotifyTypeEnum.REFUND.getType()), - eq(refund.getId())); - } - - @Test - public void testSyncRefund_notFound() { - // 准备参数 - PayRefundDO refund = randomPojo(PayRefundDO.class, o -> o.setAppId(1L) - .setStatus(PayRefundStatusEnum.WAITING.getStatus())); - refundMapper.insert(refund); - - // 调用 - int count = refundService.syncRefund(); - // 断言 - assertEquals(count, 0); - } - - @Test - public void testSyncRefund_waiting() { - assertEquals(testSyncRefund_waitingOrSuccessOrFailure(PayRefundStatusEnum.WAITING.getStatus()), 0); - } - - @Test - public void testSyncRefund_success() { - assertEquals(testSyncRefund_waitingOrSuccessOrFailure(PayRefundStatusEnum.SUCCESS.getStatus()), 1); - } - - @Test - public void testSyncRefund_failure() { - assertEquals(testSyncRefund_waitingOrSuccessOrFailure(PayRefundStatusEnum.FAILURE.getStatus()), 1); - } - - private int testSyncRefund_waitingOrSuccessOrFailure(Integer status) { - PayRefundServiceImpl payRefundServiceImpl = mock(PayRefundServiceImpl.class); - try (MockedStatic springUtilMockedStatic = mockStatic(SpringUtil.class)) { - springUtilMockedStatic.when(() -> SpringUtil.getBean(eq(PayRefundServiceImpl.class))) - .thenReturn(payRefundServiceImpl); - - // 准备参数 - PayRefundDO refund = randomPojo(PayRefundDO.class, o -> o.setAppId(1L).setChannelId(10L) - .setStatus(PayRefundStatusEnum.WAITING.getStatus()) - .setOrderNo("P110").setNo("R220")); - refundMapper.insert(refund); - // mock 方法(client) - PayClient client = mock(PayClient.class); - when(channelService.getPayClient(eq(10L))).thenReturn(client); - // mock 方法(client 返回指定状态) - PayRefundRespDTO respDTO = randomPojo(PayRefundRespDTO.class, o -> o.setStatus(status)); - when(client.getRefund(eq("P110"), eq("R220"))).thenReturn(respDTO); - // mock 方法(channel) - PayChannelDO channel = randomPojo(PayChannelDO.class, o -> o.setId(10L)); - when(channelService.validPayChannel(eq(10L))).thenReturn(channel); - - // 调用 - return refundService.syncRefund(); - } - } - - @Test - public void testSyncRefund_exception() { - // 准备参数 - PayRefundDO refund = randomPojo(PayRefundDO.class, o -> o.setAppId(1L).setChannelId(10L) - .setStatus(PayRefundStatusEnum.WAITING.getStatus()) - .setOrderNo("P110").setNo("R220")); - refundMapper.insert(refund); - // mock 方法(client) - PayClient client = mock(PayClient.class); - when(channelService.getPayClient(eq(10L))).thenReturn(client); - // mock 方法(client 抛出异常) - when(client.getRefund(eq("P110"), eq("R220"))).thenThrow(new RuntimeException()); - - // 调用 - int count = refundService.syncRefund(); - // 断言 - assertEquals(count, 0); - } - -} diff --git a/yudao-module-pay/yudao-module-pay-biz/src/test/resources/application-unit-test.yaml b/yudao-module-pay/yudao-module-pay-biz/src/test/resources/application-unit-test.yaml deleted file mode 100644 index f05f050..0000000 --- a/yudao-module-pay/yudao-module-pay-biz/src/test/resources/application-unit-test.yaml +++ /dev/null @@ -1,48 +0,0 @@ -spring: - main: - lazy-initialization: true # 开启懒加载,加快速度 - banner-mode: off # 单元测试,禁用 Banner - ---- #################### 数据库相关配置 #################### - -spring: - # 数据源配置项 - datasource: - name: ruoyi-vue-pro - url: jdbc:h2:mem:testdb;MODE=MYSQL;DATABASE_TO_UPPER=false;NON_KEYWORDS=value; # MODE 使用 MySQL 模式;DATABASE_TO_UPPER 配置表和字段使用小写 - driver-class-name: org.h2.Driver - username: sa - password: - druid: - async-init: true # 单元测试,异步初始化 Druid 连接池,提升启动速度 - initial-size: 1 # 单元测试,配置为 1,提升启动速度 - sql: - init: - schema-locations: classpath:/sql/create_tables.sql - - # Redis 配置。Redisson 默认的配置足够使用,一般不需要进行调优 - data: - redis: - host: 127.0.0.1 # 地址 - port: 16379 # 端口(单元测试,使用 16379 端口) - database: 0 # 数据库索引 - -mybatis: - lazy-initialization: true # 单元测试,设置 MyBatis Mapper 延迟加载,加速每个单元测试 - ---- #################### 定时任务相关配置 #################### - ---- #################### 配置中心相关配置 #################### - ---- #################### 服务保障相关配置 #################### - -# Lock4j 配置项(单元测试,禁用 Lock4j) - ---- #################### 监控相关配置 #################### - ---- #################### 芋道相关配置 #################### - -# 芋道配置项,设置当前项目所有自定义的配置 -yudao: - info: - base-package: cn.iocoder.yudao.module diff --git a/yudao-module-pay/yudao-module-pay-biz/src/test/resources/logback.xml b/yudao-module-pay/yudao-module-pay-biz/src/test/resources/logback.xml deleted file mode 100644 index daf756b..0000000 --- a/yudao-module-pay/yudao-module-pay-biz/src/test/resources/logback.xml +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/yudao-module-pay/yudao-module-pay-biz/src/test/resources/sql/clean.sql b/yudao-module-pay/yudao-module-pay-biz/src/test/resources/sql/clean.sql deleted file mode 100644 index 91fff0c..0000000 --- a/yudao-module-pay/yudao-module-pay-biz/src/test/resources/sql/clean.sql +++ /dev/null @@ -1,7 +0,0 @@ -DELETE FROM pay_app; -DELETE FROM pay_channel; -DELETE FROM pay_order; -DELETE FROM pay_order_extension; -DELETE FROM pay_refund; -DELETE FROM pay_notify_task; -DELETE FROM pay_notify_log; diff --git a/yudao-module-pay/yudao-module-pay-biz/src/test/resources/sql/create_tables.sql b/yudao-module-pay/yudao-module-pay-biz/src/test/resources/sql/create_tables.sql deleted file mode 100644 index 3f9f764..0000000 --- a/yudao-module-pay/yudao-module-pay-biz/src/test/resources/sql/create_tables.sql +++ /dev/null @@ -1,147 +0,0 @@ -CREATE TABLE IF NOT EXISTS "pay_app" ( - "id" number NOT NULL GENERATED BY DEFAULT AS IDENTITY, - "app_key" varchar(64) NOT NULL, - "name" varchar(64) NOT NULL, - "status" tinyint NOT NULL, - "remark" varchar(255) DEFAULT NULL, - `order_notify_url` varchar(1024) NOT NULL, - `refund_notify_url` varchar(1024) NOT NULL, - "creator" varchar(64) DEFAULT '', - "create_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, - "updater" varchar(64) DEFAULT '', - "update_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, - "deleted" bit(1) NOT NULL DEFAULT FALSE, - PRIMARY KEY ("id") -) COMMENT = '支付应用'; - -CREATE TABLE IF NOT EXISTS "pay_channel" ( - "id" number NOT NULL GENERATED BY DEFAULT AS IDENTITY, - "code" varchar(32) NOT NULL, - "status" tinyint(4) NOT NULL, - "remark" varchar(255) DEFAULT NULL, - "fee_rate" double NOT NULL DEFAULT 0, - "app_id" bigint(20) NOT NULL, - "config" varchar(10240) NOT NULL, - "creator" varchar(64) NULL DEFAULT '', - "create_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, - "updater" varchar(64) NULL DEFAULT '', - "update_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, - "deleted" bit(1) NOT NULL DEFAULT FALSE, - "tenant_id" bigint not null default '0', - PRIMARY KEY ("id") -) COMMENT = '支付渠道'; - -CREATE TABLE IF NOT EXISTS `pay_order` ( - "id" number NOT NULL GENERATED BY DEFAULT AS IDENTITY, - `app_id` bigint(20) NOT NULL, - `channel_id` bigint(20) DEFAULT NULL, - `channel_code` varchar(32) DEFAULT NULL, - `merchant_order_id` varchar(64) NOT NULL, - `subject` varchar(32) NOT NULL, - `body` varchar(128) NOT NULL, - `notify_url` varchar(1024) NOT NULL, - `price` bigint(20) NOT NULL, - `channel_fee_rate` double DEFAULT 0, - `channel_fee_price` bigint(20) DEFAULT 0, - `status` tinyint(4) NOT NULL, - `user_ip` varchar(50) NOT NULL, - `expire_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, - `success_time` datetime(0) DEFAULT CURRENT_TIMESTAMP, - `notify_time` datetime(0) DEFAULT CURRENT_TIMESTAMP, - `extension_id` bigint(20) DEFAULT NULL, - `no` varchar(64) NULL, - `refund_price` bigint(20) NOT NULL, - `channel_user_id` varchar(255) DEFAULT NULL, - `channel_order_no` varchar(64) DEFAULT NULL, - `creator` varchar(64) DEFAULT '', - `create_time` datetime(0) NOT NULL DEFAULT CURRENT_TIMESTAMP, - `updater` varchar(64) DEFAULT '', - `update_time` datetime(0) NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, - `deleted` bit(1) NOT NULL DEFAULT FALSE, - PRIMARY KEY ("id") -) COMMENT = '支付订单'; - -CREATE TABLE IF NOT EXISTS `pay_order_extension` ( - "id" number NOT NULL GENERATED BY DEFAULT AS IDENTITY, - `no` varchar(64) NOT NULL, - `order_id` bigint(20) NOT NULL, - `channel_id` bigint(20) NOT NULL, - `channel_code` varchar(32) NOT NULL, - `user_ip` varchar(50) NULL DEFAULT NULL, - `status` tinyint(4) NOT NULL, - `channel_extras` varchar(1024) NULL DEFAULT NULL, - `channel_error_code` varchar(64) NULL, - `channel_error_msg` varchar(64) NULL, - `channel_notify_data` varchar(1024) NULL, - `creator` varchar(64) NULL DEFAULT '', - `create_time` datetime(0) NOT NULL DEFAULT CURRENT_TIMESTAMP, - `updater` varchar(64) NULL DEFAULT '', - `update_time` datetime(0) NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, - `deleted` bit(1) NOT NULL DEFAULT FALSE, - PRIMARY KEY ("id") -) COMMENT = '支付订单拓展'; - -CREATE TABLE IF NOT EXISTS `pay_refund` ( - "id" number NOT NULL GENERATED BY DEFAULT AS IDENTITY, - `no` varchar(64) NOT NULL, - `app_id` bigint(20) NOT NULL, - `channel_id` bigint(20) NOT NULL, - `channel_code` varchar(32) NOT NULL, - `order_id` bigint(20) NOT NULL, - `order_no` varchar(64) NOT NULL, - `merchant_order_id` varchar(64) NOT NULL, - `merchant_refund_id` varchar(64) NOT NULL, - `notify_url` varchar(1024) NOT NULL, - `status` tinyint(4) NOT NULL, - `pay_price` bigint(20) NOT NULL, - `refund_price` bigint(20) NOT NULL, - `reason` varchar(256) NOT NULL, - `user_ip` varchar(50) NULL DEFAULT NULL, - `channel_order_no` varchar(64) NOT NULL, - `channel_refund_no` varchar(64) NULL DEFAULT NULL, - `success_time` datetime(0) NULL DEFAULT NULL, - `channel_error_code` varchar(128) NULL DEFAULT NULL, - `channel_error_msg` varchar(256) NULL DEFAULT NULL, - `channel_notify_data` varchar(1024) NULL, - `creator` varchar(64) NULL DEFAULT '', - `create_time` datetime(0) NOT NULL DEFAULT CURRENT_TIMESTAMP, - `updater` varchar(64) NULL DEFAULT '', - `update_time` datetime(0) NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, - `deleted` bit(1) NOT NULL DEFAULT FALSE, - PRIMARY KEY ("id") -) COMMENT = '退款订单'; - -CREATE TABLE IF NOT EXISTS `pay_notify_task` ( - "id" number NOT NULL GENERATED BY DEFAULT AS IDENTITY, - `app_id` bigint(20) NOT NULL, - `type` tinyint(4) NOT NULL, - `data_id` bigint(20) NOT NULL, - `merchant_order_id` varchar(64) NOT NULL, - `status` tinyint(4) NOT NULL, - `next_notify_time` datetime(0) NULL DEFAULT NULL, - `last_execute_time` datetime(0) NULL DEFAULT NULL, - `notify_times` int NOT NULL, - `max_notify_times` int NOT NULL, - `notify_url` varchar(1024) NOT NULL, - `creator` varchar(64) NULL DEFAULT '', - `create_time` datetime(0) NOT NULL DEFAULT CURRENT_TIMESTAMP, - `updater` varchar(64) NULL DEFAULT '', - `update_time` datetime(0) NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, - `deleted` bit(1) NOT NULL DEFAULT FALSE, - `tenant_id` bigint(20) NOT NULL DEFAULT 0, - PRIMARY KEY ("id") -) COMMENT = '支付通知任务'; - -CREATE TABLE IF NOT EXISTS `pay_notify_log` ( - "id" number NOT NULL GENERATED BY DEFAULT AS IDENTITY, - `task_id` bigint(20) NOT NULL, - `notify_times` int NOT NULL, - `response` varchar(1024) NOT NULL, - `status` tinyint(4) NOT NULL, - `creator` varchar(64) NULL DEFAULT '', - `create_time` datetime(0) NOT NULL DEFAULT CURRENT_TIMESTAMP, - `updater` varchar(64) NULL DEFAULT '', - `update_time` datetime(0) NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, - `deleted` bit(1) NOT NULL DEFAULT FALSE, - PRIMARY KEY ("id") -) COMMENT = '支付通知日志'; diff --git a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/controller/admin/socail/SocialUserController.java b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/controller/admin/socail/SocialUserController.java index 643e465..77a77da 100644 --- a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/controller/admin/socail/SocialUserController.java +++ b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/controller/admin/socail/SocialUserController.java @@ -4,11 +4,11 @@ import cn.iocoder.yudao.framework.common.enums.UserTypeEnum; 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.module.system.api.social.dto.SocialUserBindReqDTO; import cn.iocoder.yudao.module.system.controller.admin.socail.vo.user.SocialUserBindReqVO; import cn.iocoder.yudao.module.system.controller.admin.socail.vo.user.SocialUserPageReqVO; import cn.iocoder.yudao.module.system.controller.admin.socail.vo.user.SocialUserRespVO; import cn.iocoder.yudao.module.system.controller.admin.socail.vo.user.SocialUserUnbindReqVO; -import cn.iocoder.yudao.module.system.convert.social.SocialUserConvert; import cn.iocoder.yudao.module.system.dal.dataobject.social.SocialUserDO; import cn.iocoder.yudao.module.system.service.social.SocialUserService; import io.swagger.v3.oas.annotations.Operation; @@ -38,8 +38,8 @@ public class SocialUserController { @PostMapping("/bind") @Operation(summary = "社交绑定,使用 code 授权码") public CommonResult socialBind(@RequestBody @Valid SocialUserBindReqVO reqVO) { - socialUserService.bindSocialUser(SocialUserConvert.INSTANCE.convert( - getLoginUserId(), UserTypeEnum.ADMIN.getValue(), reqVO)); + socialUserService.bindSocialUser(BeanUtils.toBean(reqVO, SocialUserBindReqDTO.class) + .setUserId(getLoginUserId()).setUserType(UserTypeEnum.ADMIN.getValue())); return CommonResult.success(true); } diff --git a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/convert/social/SocialUserConvert.java b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/convert/social/SocialUserConvert.java deleted file mode 100644 index 9e679a2..0000000 --- a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/convert/social/SocialUserConvert.java +++ /dev/null @@ -1,17 +0,0 @@ -package cn.iocoder.yudao.module.system.convert.social; - -import cn.iocoder.yudao.module.system.api.social.dto.SocialUserBindReqDTO; -import cn.iocoder.yudao.module.system.controller.admin.socail.vo.user.SocialUserBindReqVO; -import org.mapstruct.Mapper; -import org.mapstruct.Mapping; -import org.mapstruct.factory.Mappers; - -@Mapper -public interface SocialUserConvert { - - SocialUserConvert INSTANCE = Mappers.getMapper(SocialUserConvert.class); - - @Mapping(source = "reqVO.type", target = "socialType") - SocialUserBindReqDTO convert(Long userId, Integer userType, SocialUserBindReqVO reqVO); - -} diff --git a/yudao-server/pom.xml b/yudao-server/pom.xml index 0c4e549..6d5ee9c 100644 --- a/yudao-server/pom.xml +++ b/yudao-server/pom.xml @@ -54,7 +54,7 @@ - + -- libgit2 0.21.4