Commit f108d478a8206b5a0a76968014d6e7001235a31b
1 parent
1c5544fc
调整渠道支付通知地址为统一的地址
Showing
14 changed files
with
332 additions
and
232 deletions
yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/enums/PayErrorCodeCoreConstants.java
| @@ -24,9 +24,9 @@ public interface PayErrorCodeCoreConstants { | @@ -24,9 +24,9 @@ public interface PayErrorCodeCoreConstants { | ||
| 24 | ErrorCode CHANNEL_NOT_EXISTS = new ErrorCode(1007001003, "支付渠道不存在"); | 24 | ErrorCode CHANNEL_NOT_EXISTS = new ErrorCode(1007001003, "支付渠道不存在"); |
| 25 | ErrorCode CHANNEL_EXIST_SAME_CHANNEL_ERROR = new ErrorCode(1007001005, "已存在相同的渠道"); | 25 | ErrorCode CHANNEL_EXIST_SAME_CHANNEL_ERROR = new ErrorCode(1007001005, "已存在相同的渠道"); |
| 26 | ErrorCode CHANNEL_WECHAT_VERSION_2_MCH_KEY_IS_NULL = new ErrorCode(1007001006,"微信渠道v2版本中商户密钥不可为空"); | 26 | ErrorCode CHANNEL_WECHAT_VERSION_2_MCH_KEY_IS_NULL = new ErrorCode(1007001006,"微信渠道v2版本中商户密钥不可为空"); |
| 27 | - ErrorCode CHANNEL_WECHAT_VERSION_3_PRIVATE_KEY_IS_NULL = new ErrorCode(1007001006,"微信渠道v3版本apiclient_key.pem不可为空"); | ||
| 28 | - ErrorCode CHANNEL_WECHAT_VERSION_3_CERT_KEY_IS_NULL = new ErrorCode(1007001006,"微信渠道v3版本中apiclient_cert.pem不可为空"); | ||
| 29 | - | 27 | + ErrorCode CHANNEL_WECHAT_VERSION_3_PRIVATE_KEY_IS_NULL = new ErrorCode(1007001007,"微信渠道v3版本apiclient_key.pem不可为空"); |
| 28 | + ErrorCode CHANNEL_WECHAT_VERSION_3_CERT_KEY_IS_NULL = new ErrorCode(1007001008,"微信渠道v3版本中apiclient_cert.pem不可为空"); | ||
| 29 | + ErrorCode PAY_CHANNEL_NOTIFY_VERIFY_FAILED = new ErrorCode(1007001009, "渠道通知校验失败"); | ||
| 30 | /** | 30 | /** |
| 31 | * ========== ORDER 模块 1-007-002-000 ========== | 31 | * ========== ORDER 模块 1-007-002-000 ========== |
| 32 | */ | 32 | */ |
yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/service/order/PayCommonCoreService.java
0 → 100644
| 1 | +package cn.iocoder.yudao.coreservice.modules.pay.service.order; | ||
| 2 | + | ||
| 3 | +import cn.iocoder.yudao.framework.pay.core.client.dto.PayNotifyDataDTO; | ||
| 4 | + | ||
| 5 | +/** | ||
| 6 | + * 支付通用 Core Service | ||
| 7 | + * | ||
| 8 | + * @author jason | ||
| 9 | + */ | ||
| 10 | +public interface PayCommonCoreService { | ||
| 11 | + | ||
| 12 | + /** | ||
| 13 | + * 验证是否是渠道通知 | ||
| 14 | + * @param notifyData 通知数据 | ||
| 15 | + */ | ||
| 16 | + void verifyNotifyData(Long channelId, PayNotifyDataDTO notifyData); | ||
| 17 | + | ||
| 18 | + /** | ||
| 19 | + * 支付宝的支付回调通知,和退款回调通知 地址是同一个 | ||
| 20 | + * 是否是退款回调通知 | ||
| 21 | + * @param notifyData 通知数据 | ||
| 22 | + * @return | ||
| 23 | + */ | ||
| 24 | + boolean isRefundNotify(Long channelId, PayNotifyDataDTO notifyData); | ||
| 25 | +} |
yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/service/order/PayOrderCoreService.java
| @@ -43,10 +43,9 @@ public interface PayOrderCoreService { | @@ -43,10 +43,9 @@ public interface PayOrderCoreService { | ||
| 43 | * 通知支付单成功 | 43 | * 通知支付单成功 |
| 44 | * | 44 | * |
| 45 | * @param channelId 渠道编号 | 45 | * @param channelId 渠道编号 |
| 46 | - * @param channelCode 渠道编码 | ||
| 47 | * @param notifyData 通知数据 | 46 | * @param notifyData 通知数据 |
| 48 | */ | 47 | */ |
| 49 | - void notifyPayOrder(Long channelId, String channelCode, PayNotifyDataDTO notifyData) throws Exception; | 48 | + void notifyPayOrder(Long channelId, PayNotifyDataDTO notifyData) throws Exception; |
| 50 | 49 | ||
| 51 | 50 | ||
| 52 | 51 |
yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/service/order/PayRefundCoreService.java
| @@ -23,11 +23,10 @@ public interface PayRefundCoreService { | @@ -23,11 +23,10 @@ public interface PayRefundCoreService { | ||
| 23 | /** | 23 | /** |
| 24 | * 渠道的退款通知 | 24 | * 渠道的退款通知 |
| 25 | * @param channelId 渠道编号 | 25 | * @param channelId 渠道编号 |
| 26 | - * @param channelCode 渠道编码 | ||
| 27 | * @param notifyData 通知数据 | 26 | * @param notifyData 通知数据 |
| 28 | * @throws Exception 退款通知异常 | 27 | * @throws Exception 退款通知异常 |
| 29 | */ | 28 | */ |
| 30 | - void notifyPayRefund(Long channelId, String channelCode, PayNotifyDataDTO notifyData) throws Exception; | 29 | + void notifyPayRefund(Long channelId, PayNotifyDataDTO notifyData) throws Exception; |
| 31 | 30 | ||
| 32 | 31 | ||
| 33 | 32 |
yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/service/order/impl/PayCommonCoreServiceImpl.java
0 → 100644
| 1 | +package cn.iocoder.yudao.coreservice.modules.pay.service.order.impl; | ||
| 2 | + | ||
| 3 | +import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.merchant.PayChannelDO; | ||
| 4 | +import cn.iocoder.yudao.coreservice.modules.pay.service.merchant.PayChannelCoreService; | ||
| 5 | +import cn.iocoder.yudao.coreservice.modules.pay.service.order.PayCommonCoreService; | ||
| 6 | +import cn.iocoder.yudao.framework.pay.core.client.PayClient; | ||
| 7 | +import cn.iocoder.yudao.framework.pay.core.client.PayClientFactory; | ||
| 8 | +import cn.iocoder.yudao.framework.pay.core.client.dto.PayNotifyDataDTO; | ||
| 9 | +import lombok.extern.slf4j.Slf4j; | ||
| 10 | +import org.springframework.stereotype.Service; | ||
| 11 | + | ||
| 12 | +import javax.annotation.Resource; | ||
| 13 | + | ||
| 14 | +import static cn.iocoder.yudao.coreservice.modules.pay.enums.PayErrorCodeCoreConstants.PAY_CHANNEL_CLIENT_NOT_FOUND; | ||
| 15 | +import static cn.iocoder.yudao.coreservice.modules.pay.enums.PayErrorCodeCoreConstants.PAY_CHANNEL_NOTIFY_VERIFY_FAILED; | ||
| 16 | +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; | ||
| 17 | + | ||
| 18 | +/** | ||
| 19 | + * 支付通用 Core Service 实现类 | ||
| 20 | + * | ||
| 21 | + * @author jason | ||
| 22 | + */ | ||
| 23 | +@Service | ||
| 24 | +@Slf4j | ||
| 25 | +public class PayCommonCoreServiceImpl implements PayCommonCoreService { | ||
| 26 | + | ||
| 27 | + @Resource | ||
| 28 | + private PayChannelCoreService payChannelCoreService; | ||
| 29 | + | ||
| 30 | + @Resource | ||
| 31 | + private PayClientFactory payClientFactory; | ||
| 32 | + | ||
| 33 | + @Override | ||
| 34 | + public void verifyNotifyData(Long channelId, PayNotifyDataDTO notifyData) { | ||
| 35 | + // 校验支付渠道是否有效 | ||
| 36 | + PayChannelDO channel = payChannelCoreService.validPayChannel(channelId); | ||
| 37 | + // 校验支付客户端是否正确初始化 | ||
| 38 | + PayClient client = payClientFactory.getPayClient(channel.getId()); | ||
| 39 | + if (client == null) { | ||
| 40 | + log.error("[notifyPayOrder][渠道编号({}) 找不到对应的支付客户端]", channel.getId()); | ||
| 41 | + throw exception(PAY_CHANNEL_CLIENT_NOT_FOUND); | ||
| 42 | + } | ||
| 43 | + boolean verifyResult = client.verifyNotifyData(notifyData); | ||
| 44 | + if(!verifyResult){ | ||
| 45 | + //渠道通知验证失败 | ||
| 46 | + throw exception(PAY_CHANNEL_NOTIFY_VERIFY_FAILED); | ||
| 47 | + } | ||
| 48 | + } | ||
| 49 | + | ||
| 50 | + @Override | ||
| 51 | + public boolean isRefundNotify(Long channelId, PayNotifyDataDTO notifyData) { | ||
| 52 | + // 校验支付渠道是否有效 | ||
| 53 | + PayChannelDO channel = payChannelCoreService.validPayChannel(channelId); | ||
| 54 | + // 校验支付客户端是否正确初始化 | ||
| 55 | + PayClient client = payClientFactory.getPayClient(channel.getId()); | ||
| 56 | + if (client == null) { | ||
| 57 | + log.error("[notifyPayOrder][渠道编号({}) 找不到对应的支付客户端]", channel.getId()); | ||
| 58 | + throw exception(PAY_CHANNEL_CLIENT_NOT_FOUND); | ||
| 59 | + } | ||
| 60 | + return client.isRefundNotify(notifyData); | ||
| 61 | + } | ||
| 62 | +} |
yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/service/order/impl/PayOrderCoreServiceImpl.java
| @@ -155,24 +155,22 @@ public class PayOrderCoreServiceImpl implements PayOrderCoreService { | @@ -155,24 +155,22 @@ public class PayOrderCoreServiceImpl implements PayOrderCoreService { | ||
| 155 | 155 | ||
| 156 | /** | 156 | /** |
| 157 | * 根据支付渠道的编码,生成支付渠道的返回地址 | 157 | * 根据支付渠道的编码,生成支付渠道的返回地址 |
| 158 | - * @param channel | ||
| 159 | - * @return | 158 | + * @param channel 支付渠道 |
| 159 | + * @return 支付成功返回的地址。 配置地址 + "/" + channel id | ||
| 160 | */ | 160 | */ |
| 161 | private String genChannelReturnUrl(PayChannelDO channel) { | 161 | private String genChannelReturnUrl(PayChannelDO channel) { |
| 162 | - return payProperties.getPayReturnUrl() + "/" + StrUtil.replace(channel.getCode(), "_", "-") | ||
| 163 | - + "/" + channel.getId(); | 162 | + return payProperties.getPayReturnUrl() + "/" + channel.getId(); |
| 164 | } | 163 | } |
| 165 | 164 | ||
| 166 | /** | 165 | /** |
| 167 | * 根据支付渠道的编码,生成支付渠道的回调地址 | 166 | * 根据支付渠道的编码,生成支付渠道的回调地址 |
| 168 | * | 167 | * |
| 169 | * @param channel 支付渠道 | 168 | * @param channel 支付渠道 |
| 170 | - * @return 支付渠道的回调地址 | 169 | + * @return 支付渠道的回调地址 配置地址 + "/" + channel id |
| 171 | */ | 170 | */ |
| 172 | private String genChannelPayNotifyUrl(PayChannelDO channel) { | 171 | private String genChannelPayNotifyUrl(PayChannelDO channel) { |
| 173 | - // _ 转化为 - 的原因,是因为 URL 我们统一采用中划线的原则 | ||
| 174 | - return payProperties.getPayNotifyUrl() + "/" + StrUtil.replace(channel.getCode(), "_", "-") | ||
| 175 | - + "/" + channel.getId(); | 172 | + //去掉channel code, 似乎没啥用, 用统一的回调地址 |
| 173 | + return payProperties.getPayNotifyUrl() + "/" + channel.getId(); | ||
| 176 | } | 174 | } |
| 177 | 175 | ||
| 178 | private String generateOrderExtensionNo() { | 176 | private String generateOrderExtensionNo() { |
| @@ -195,7 +193,7 @@ public class PayOrderCoreServiceImpl implements PayOrderCoreService { | @@ -195,7 +193,7 @@ public class PayOrderCoreServiceImpl implements PayOrderCoreService { | ||
| 195 | 193 | ||
| 196 | @Override | 194 | @Override |
| 197 | @Transactional | 195 | @Transactional |
| 198 | - public void notifyPayOrder(Long channelId, String channelCode, PayNotifyDataDTO notifyData) throws Exception { | 196 | + public void notifyPayOrder(Long channelId, PayNotifyDataDTO notifyData) throws Exception { |
| 199 | // TODO 芋艿,记录回调日志 | 197 | // TODO 芋艿,记录回调日志 |
| 200 | log.info("[notifyPayOrder][channelId({}) 回调数据({})]", channelId, notifyData.getBody()); | 198 | log.info("[notifyPayOrder][channelId({}) 回调数据({})]", channelId, notifyData.getBody()); |
| 201 | 199 | ||
| @@ -207,7 +205,7 @@ public class PayOrderCoreServiceImpl implements PayOrderCoreService { | @@ -207,7 +205,7 @@ public class PayOrderCoreServiceImpl implements PayOrderCoreService { | ||
| 207 | log.error("[notifyPayOrder][渠道编号({}) 找不到对应的支付客户端]", channel.getId()); | 205 | log.error("[notifyPayOrder][渠道编号({}) 找不到对应的支付客户端]", channel.getId()); |
| 208 | throw exception(PAY_CHANNEL_CLIENT_NOT_FOUND); | 206 | throw exception(PAY_CHANNEL_CLIENT_NOT_FOUND); |
| 209 | } | 207 | } |
| 210 | - //TODO @jason 校验 是否支付宝调用。 使用 支付宝publickey 或者payclient 加一个校验方法 | 208 | + |
| 211 | // 解析支付结果 | 209 | // 解析支付结果 |
| 212 | PayOrderNotifyRespDTO notifyRespDTO = client.parseOrderNotify(notifyData); | 210 | PayOrderNotifyRespDTO notifyRespDTO = client.parseOrderNotify(notifyData); |
| 213 | 211 | ||
| @@ -222,7 +220,7 @@ public class PayOrderCoreServiceImpl implements PayOrderCoreService { | @@ -222,7 +220,7 @@ public class PayOrderCoreServiceImpl implements PayOrderCoreService { | ||
| 222 | throw exception(PAY_ORDER_EXTENSION_STATUS_IS_NOT_WAITING); | 220 | throw exception(PAY_ORDER_EXTENSION_STATUS_IS_NOT_WAITING); |
| 223 | } | 221 | } |
| 224 | // 1.2 更新 PayOrderExtensionDO | 222 | // 1.2 更新 PayOrderExtensionDO |
| 225 | - //TODO @jason notifyRespDTO.getTradeStatus() 需要根据不同的状态更新成不同的值 PayOrderStatusEnum | 223 | + //TODO 支付宝交易超时 TRADE_FINISHED 需要更新交易关闭 |
| 226 | int updateCounts = payOrderExtensionCoreMapper.updateByIdAndStatus(orderExtension.getId(), | 224 | int updateCounts = payOrderExtensionCoreMapper.updateByIdAndStatus(orderExtension.getId(), |
| 227 | PayOrderStatusEnum.WAITING.getStatus(), PayOrderExtensionDO.builder().id(orderExtension.getId()) | 225 | PayOrderStatusEnum.WAITING.getStatus(), PayOrderExtensionDO.builder().id(orderExtension.getId()) |
| 228 | .status(PayOrderStatusEnum.SUCCESS.getStatus()).channelNotifyData(notifyData.getBody()).build()); | 226 | .status(PayOrderStatusEnum.SUCCESS.getStatus()).channelNotifyData(notifyData.getBody()).build()); |
| @@ -241,7 +239,7 @@ public class PayOrderCoreServiceImpl implements PayOrderCoreService { | @@ -241,7 +239,7 @@ public class PayOrderCoreServiceImpl implements PayOrderCoreService { | ||
| 241 | } | 239 | } |
| 242 | // 2.2 更新 PayOrderDO | 240 | // 2.2 更新 PayOrderDO |
| 243 | updateCounts = payOrderCoreMapper.updateByIdAndStatus(order.getId(), PayOrderStatusEnum.WAITING.getStatus(), | 241 | updateCounts = payOrderCoreMapper.updateByIdAndStatus(order.getId(), PayOrderStatusEnum.WAITING.getStatus(), |
| 244 | - PayOrderDO.builder().status(PayOrderStatusEnum.SUCCESS.getStatus()).channelId(channelId).channelCode(channelCode) | 242 | + PayOrderDO.builder().status(PayOrderStatusEnum.SUCCESS.getStatus()).channelId(channelId).channelCode(channel.getCode()) |
| 245 | .successTime(notifyRespDTO.getSuccessTime()).successExtensionId(orderExtension.getId()) | 243 | .successTime(notifyRespDTO.getSuccessTime()).successExtensionId(orderExtension.getId()) |
| 246 | .channelOrderNo(notifyRespDTO.getChannelOrderNo()).channelUserId(notifyRespDTO.getChannelUserId()) | 244 | .channelOrderNo(notifyRespDTO.getChannelOrderNo()).channelUserId(notifyRespDTO.getChannelUserId()) |
| 247 | .notifyTime(new Date()).build()); | 245 | .notifyTime(new Date()).build()); |
yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/service/order/impl/PayRefundChannelQueryHandler.java
| @@ -40,7 +40,9 @@ public class PayRefundChannelQueryHandler extends PayRefundAbstractChannelPostHa | @@ -40,7 +40,9 @@ public class PayRefundChannelQueryHandler extends PayRefundAbstractChannelPostHa | ||
| 40 | //更新退款单表 | 40 | //更新退款单表 |
| 41 | PayRefundDO updateRefundDO = new PayRefundDO(); | 41 | PayRefundDO updateRefundDO = new PayRefundDO(); |
| 42 | updateRefundDO.setId(respBO.getRefundId()) | 42 | updateRefundDO.setId(respBO.getRefundId()) |
| 43 | - .setStatus(refundStatus.getStatus()); | 43 | + .setStatus(refundStatus.getStatus()) |
| 44 | + .setChannelErrorCode(respBO.getChannelErrCode()) | ||
| 45 | + .setChannelErrorMsg(respBO.getChannelErrMsg()); | ||
| 44 | updatePayRefund(updateRefundDO); | 46 | updatePayRefund(updateRefundDO); |
| 45 | 47 | ||
| 46 | PayOrderDO updateOrderDO = new PayOrderDO(); | 48 | PayOrderDO updateOrderDO = new PayOrderDO(); |
yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/service/order/impl/PayRefundCoreServiceImpl.java
| @@ -182,7 +182,7 @@ public class PayRefundCoreServiceImpl implements PayRefundCoreService { | @@ -182,7 +182,7 @@ public class PayRefundCoreServiceImpl implements PayRefundCoreService { | ||
| 182 | 182 | ||
| 183 | 183 | ||
| 184 | @Override | 184 | @Override |
| 185 | - public void notifyPayRefund(Long channelId, String channelCode, PayNotifyDataDTO notifyData) { | 185 | + public void notifyPayRefund(Long channelId, PayNotifyDataDTO notifyData) { |
| 186 | log.info("[notifyPayRefund][channelId({}) 回调数据({})]", channelId, notifyData.getBody()); | 186 | log.info("[notifyPayRefund][channelId({}) 回调数据({})]", channelId, notifyData.getBody()); |
| 187 | // 校验支付渠道是否有效 | 187 | // 校验支付渠道是否有效 |
| 188 | PayChannelDO channel = payChannelCoreService.validPayChannel(channelId); | 188 | PayChannelDO channel = payChannelCoreService.validPayChannel(channelId); |
yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/PayClient.java
| @@ -49,4 +49,22 @@ public interface PayClient { | @@ -49,4 +49,22 @@ public interface PayClient { | ||
| 49 | */ | 49 | */ |
| 50 | PayRefundNotifyDTO parseRefundNotify(PayNotifyDataDTO notifyData); | 50 | PayRefundNotifyDTO parseRefundNotify(PayNotifyDataDTO notifyData); |
| 51 | 51 | ||
| 52 | + | ||
| 53 | + /** | ||
| 54 | + * 验证是否渠道通知 | ||
| 55 | + * @param notifyData 通知数据 | ||
| 56 | + * @return 默认是 true | ||
| 57 | + */ | ||
| 58 | + default boolean verifyNotifyData(PayNotifyDataDTO notifyData){ | ||
| 59 | + return true; | ||
| 60 | + } | ||
| 61 | + | ||
| 62 | + /** | ||
| 63 | + * 是否退款通知 | ||
| 64 | + * @param notifyData 通知数据 | ||
| 65 | + * @return 默认是 false | ||
| 66 | + */ | ||
| 67 | + default boolean isRefundNotify(PayNotifyDataDTO notifyData){ | ||
| 68 | + return false; | ||
| 69 | + } | ||
| 52 | } | 70 | } |
yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/alipay/AbstractAlipayClient.java
0 → 100644
| 1 | +package cn.iocoder.yudao.framework.pay.core.client.impl.alipay; | ||
| 2 | + | ||
| 3 | +import cn.hutool.core.bean.BeanUtil; | ||
| 4 | +import cn.hutool.core.date.DateUtil; | ||
| 5 | +import cn.iocoder.yudao.framework.pay.core.client.AbstractPayCodeMapping; | ||
| 6 | +import cn.iocoder.yudao.framework.pay.core.client.dto.*; | ||
| 7 | +import cn.iocoder.yudao.framework.pay.core.client.impl.AbstractPayClient; | ||
| 8 | +import cn.iocoder.yudao.framework.pay.core.enums.PayChannelRespEnum; | ||
| 9 | +import cn.iocoder.yudao.framework.pay.core.enums.PayNotifyRefundStatusEnum; | ||
| 10 | +import com.alipay.api.AlipayApiException; | ||
| 11 | +import com.alipay.api.AlipayConfig; | ||
| 12 | +import com.alipay.api.DefaultAlipayClient; | ||
| 13 | +import com.alipay.api.domain.AlipayTradeRefundModel; | ||
| 14 | +import com.alipay.api.internal.util.AlipaySignature; | ||
| 15 | +import com.alipay.api.request.AlipayTradeRefundRequest; | ||
| 16 | +import com.alipay.api.response.AlipayTradeRefundResponse; | ||
| 17 | +import lombok.SneakyThrows; | ||
| 18 | +import lombok.extern.slf4j.Slf4j; | ||
| 19 | + | ||
| 20 | +import java.net.SocketTimeoutException; | ||
| 21 | +import java.nio.charset.StandardCharsets; | ||
| 22 | +import java.util.Map; | ||
| 23 | + | ||
| 24 | +import static cn.iocoder.yudao.framework.common.util.json.JsonUtils.toJsonString; | ||
| 25 | + | ||
| 26 | +/** | ||
| 27 | + * 支付宝抽象类, 实现支付宝统一的接口。如退款 | ||
| 28 | + * | ||
| 29 | + * @author jason | ||
| 30 | + */ | ||
| 31 | +@Slf4j | ||
| 32 | +public abstract class AbstractAlipayClient extends AbstractPayClient<AlipayPayClientConfig> { | ||
| 33 | + | ||
| 34 | + protected DefaultAlipayClient client; | ||
| 35 | + | ||
| 36 | + public AbstractAlipayClient(Long channelId, String channelCode, | ||
| 37 | + AlipayPayClientConfig config, AbstractPayCodeMapping codeMapping) { | ||
| 38 | + super(channelId, channelCode, config, codeMapping); | ||
| 39 | + } | ||
| 40 | + | ||
| 41 | + @Override | ||
| 42 | + @SneakyThrows | ||
| 43 | + protected void doInit() { | ||
| 44 | + AlipayConfig alipayConfig = new AlipayConfig(); | ||
| 45 | + BeanUtil.copyProperties(config, alipayConfig, false); | ||
| 46 | + this.client = new DefaultAlipayClient(alipayConfig); | ||
| 47 | + } | ||
| 48 | + | ||
| 49 | + /** | ||
| 50 | + * 从支付宝通知返回参数中解析 PayOrderNotifyRespDTO, 通知具体参数参考 | ||
| 51 | + * //https://opendocs.alipay.com/open/203/105286 | ||
| 52 | + * @param data 通知结果 | ||
| 53 | + * @return 解析结果 PayOrderNotifyRespDTO | ||
| 54 | + * @throws Exception 解析失败,抛出异常 | ||
| 55 | + */ | ||
| 56 | + @Override | ||
| 57 | + public PayOrderNotifyRespDTO parseOrderNotify(PayNotifyDataDTO data) throws Exception { | ||
| 58 | + Map<String, String> params = data.getParams(); | ||
| 59 | + return PayOrderNotifyRespDTO.builder().orderExtensionNo(params.get("out_trade_no")) | ||
| 60 | + .channelOrderNo(params.get("trade_no")).channelUserId(params.get("seller_id")) | ||
| 61 | + .tradeStatus(params.get("trade_status")) | ||
| 62 | + .successTime(DateUtil.parse(params.get("notify_time"), "yyyy-MM-dd HH:mm:ss")) | ||
| 63 | + .data(data.getBody()).build(); | ||
| 64 | + } | ||
| 65 | + | ||
| 66 | + @Override | ||
| 67 | + public PayRefundNotifyDTO parseRefundNotify(PayNotifyDataDTO notifyData) { | ||
| 68 | + Map<String, String> params = notifyData.getParams(); | ||
| 69 | + PayRefundNotifyDTO notifyDTO = PayRefundNotifyDTO.builder().channelOrderNo(params.get("trade_no")) | ||
| 70 | + .tradeNo(params.get("out_trade_no")) | ||
| 71 | + .reqNo(params.get("out_biz_no")) | ||
| 72 | + .status(PayNotifyRefundStatusEnum.SUCCESS) | ||
| 73 | + .refundSuccessTime(DateUtil.parse(params.get("gmt_refund"), "yyyy-MM-dd HH:mm:ss")) | ||
| 74 | + .build(); | ||
| 75 | + return notifyDTO; | ||
| 76 | + } | ||
| 77 | + | ||
| 78 | + @Override | ||
| 79 | + public boolean isRefundNotify(PayNotifyDataDTO notifyData) { | ||
| 80 | + if (notifyData.getParams().containsKey("refund_fee")) { | ||
| 81 | + return true; | ||
| 82 | + } else { | ||
| 83 | + return false; | ||
| 84 | + } | ||
| 85 | + } | ||
| 86 | + | ||
| 87 | + @Override | ||
| 88 | + public boolean verifyNotifyData(PayNotifyDataDTO notifyData) { | ||
| 89 | + boolean verifyResult = false; | ||
| 90 | + try { | ||
| 91 | + verifyResult = AlipaySignature.rsaCheckV1(notifyData.getParams(), config.getAlipayPublicKey(), StandardCharsets.UTF_8.name(), "RSA2"); | ||
| 92 | + } catch (AlipayApiException e) { | ||
| 93 | + log.error("[AlipayClient verifyNotifyData][(notify param is :{}) 验证失败]", toJsonString(notifyData.getParams()), e); | ||
| 94 | + } | ||
| 95 | + return verifyResult; | ||
| 96 | + } | ||
| 97 | + | ||
| 98 | + /** | ||
| 99 | + * 支付宝统一的退款接口 alipay.trade.refund | ||
| 100 | + * @param reqDTO 退款请求 request DTO | ||
| 101 | + * @return 退款请求 Response | ||
| 102 | + */ | ||
| 103 | + @Override | ||
| 104 | + protected PayRefundUnifiedRespDTO doUnifiedRefund(PayRefundUnifiedReqDTO reqDTO) { | ||
| 105 | + AlipayTradeRefundModel model=new AlipayTradeRefundModel(); | ||
| 106 | + model.setTradeNo(reqDTO.getChannelOrderNo()); | ||
| 107 | + model.setOutTradeNo(reqDTO.getPayTradeNo()); | ||
| 108 | + model.setOutRequestNo(reqDTO.getRefundReqNo()); | ||
| 109 | + model.setRefundAmount(calculateAmount(reqDTO.getAmount()).toString()); | ||
| 110 | + model.setRefundReason(reqDTO.getReason()); | ||
| 111 | + AlipayTradeRefundRequest refundRequest = new AlipayTradeRefundRequest(); | ||
| 112 | + refundRequest.setBizModel(model); | ||
| 113 | + PayRefundUnifiedRespDTO respDTO = new PayRefundUnifiedRespDTO(); | ||
| 114 | + try { | ||
| 115 | + AlipayTradeRefundResponse response = client.execute(refundRequest); | ||
| 116 | + log.info("[doUnifiedRefund][response({}) 发起退款 渠道返回", toJsonString(response)); | ||
| 117 | + if (response.isSuccess()) { | ||
| 118 | + //退款成功,更新为PROCESSING_NOTIFY, 而不是 SYNC_SUCCESS 通过支付宝回调接口处理。退款导致触发的异步通知, | ||
| 119 | + //退款导致触发的异步通知是发送到支付接口中设置的notify_url | ||
| 120 | + //TODO 沙箱环境 返回 的tradeNo(渠道退款单号) 和 订单的tradNo 是一个值,是不是理解不对? | ||
| 121 | + respDTO.setRespEnum(PayChannelRespEnum.PROCESSING_NOTIFY); | ||
| 122 | + }else{ | ||
| 123 | + //特殊处理 sub_code ACQ.SYSTEM_ERROR(系统错误), 需要调用重试任务 | ||
| 124 | + //沙箱环境返回的貌似是”aop.ACQ.SYSTEM_ERROR“, 用contain | ||
| 125 | + if (response.getSubCode().contains("ACQ.SYSTEM_ERROR")) { | ||
| 126 | + respDTO.setRespEnum(PayChannelRespEnum.RETRY_FAILURE) | ||
| 127 | + .setChannelErrMsg(response.getSubMsg()) | ||
| 128 | + .setChannelErrCode(response.getSubCode()); | ||
| 129 | + }else{ | ||
| 130 | + //交易已关闭,需要查询确认退款是否已经完成 | ||
| 131 | + if("ACQ.TRADE_HAS_CLOSE".equals(response.getSubCode())){ | ||
| 132 | + respDTO.setRespEnum(PayChannelRespEnum.PROCESSING_QUERY) | ||
| 133 | + .setChannelErrMsg(response.getSubMsg()) | ||
| 134 | + .setChannelErrCode(response.getSubCode()); | ||
| 135 | + }else { | ||
| 136 | + //其他当做不可以重试的错误 | ||
| 137 | + respDTO.setRespEnum(PayChannelRespEnum.CAN_NOT_RETRY_FAILURE) | ||
| 138 | + .setChannelErrCode(response.getSubCode()) | ||
| 139 | + .setChannelErrMsg(response.getSubMsg()); | ||
| 140 | + } | ||
| 141 | + } | ||
| 142 | + } | ||
| 143 | + return respDTO; | ||
| 144 | + } catch (AlipayApiException e) { | ||
| 145 | + //TODO 记录异常日志 | ||
| 146 | + log.error("[doUnifiedRefund][request({}) 发起退款失败,网络读超时,退款状态未知]", toJsonString(reqDTO), e); | ||
| 147 | + Throwable cause = e.getCause(); | ||
| 148 | + //网络 read time out 异常, 退款状态未知 | ||
| 149 | + if (cause instanceof SocketTimeoutException) { | ||
| 150 | + respDTO.setExceptionMsg(e.getMessage()) | ||
| 151 | + .setRespEnum(PayChannelRespEnum.READ_TIME_OUT_EXCEPTION); | ||
| 152 | + }else{ | ||
| 153 | + respDTO.setExceptionMsg(e.getMessage()) | ||
| 154 | + .setChannelErrCode(e.getErrCode()) | ||
| 155 | + .setChannelErrMsg(e.getErrMsg()) | ||
| 156 | + .setRespEnum(PayChannelRespEnum.CALL_EXCEPTION); | ||
| 157 | + } | ||
| 158 | + return respDTO; | ||
| 159 | + } | ||
| 160 | + } | ||
| 161 | + | ||
| 162 | +} |
yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/alipay/AlipayQrPayClient.java
| 1 | package cn.iocoder.yudao.framework.pay.core.client.impl.alipay; | 1 | package cn.iocoder.yudao.framework.pay.core.client.impl.alipay; |
| 2 | 2 | ||
| 3 | -import cn.hutool.core.bean.BeanUtil; | ||
| 4 | -import cn.hutool.core.date.DateUtil; | ||
| 5 | import cn.iocoder.yudao.framework.pay.core.client.PayCommonResult; | 3 | import cn.iocoder.yudao.framework.pay.core.client.PayCommonResult; |
| 6 | -import cn.iocoder.yudao.framework.pay.core.client.dto.*; | ||
| 7 | -import cn.iocoder.yudao.framework.pay.core.client.impl.AbstractPayClient; | 4 | +import cn.iocoder.yudao.framework.pay.core.client.dto.PayOrderUnifiedReqDTO; |
| 8 | import cn.iocoder.yudao.framework.pay.core.enums.PayChannelEnum; | 5 | import cn.iocoder.yudao.framework.pay.core.enums.PayChannelEnum; |
| 9 | import com.alipay.api.AlipayApiException; | 6 | import com.alipay.api.AlipayApiException; |
| 10 | -import com.alipay.api.AlipayConfig; | ||
| 11 | -import com.alipay.api.DefaultAlipayClient; | ||
| 12 | import com.alipay.api.domain.AlipayTradePrecreateModel; | 7 | import com.alipay.api.domain.AlipayTradePrecreateModel; |
| 13 | import com.alipay.api.request.AlipayTradePrecreateRequest; | 8 | import com.alipay.api.request.AlipayTradePrecreateRequest; |
| 14 | import com.alipay.api.response.AlipayTradePrecreateResponse; | 9 | import com.alipay.api.response.AlipayTradePrecreateResponse; |
| 15 | -import lombok.SneakyThrows; | ||
| 16 | import lombok.extern.slf4j.Slf4j; | 10 | import lombok.extern.slf4j.Slf4j; |
| 17 | 11 | ||
| 18 | -import java.util.Map; | ||
| 19 | - | ||
| 20 | import static cn.iocoder.yudao.framework.common.util.json.JsonUtils.toJsonString; | 12 | import static cn.iocoder.yudao.framework.common.util.json.JsonUtils.toJsonString; |
| 21 | 13 | ||
| 22 | /** | 14 | /** |
| @@ -26,24 +18,13 @@ import static cn.iocoder.yudao.framework.common.util.json.JsonUtils.toJsonString | @@ -26,24 +18,13 @@ import static cn.iocoder.yudao.framework.common.util.json.JsonUtils.toJsonString | ||
| 26 | * @author 芋道源码 | 18 | * @author 芋道源码 |
| 27 | */ | 19 | */ |
| 28 | @Slf4j | 20 | @Slf4j |
| 29 | -public class AlipayQrPayClient extends AbstractPayClient<AlipayPayClientConfig> { | ||
| 30 | - | ||
| 31 | - private DefaultAlipayClient client; | 21 | +public class AlipayQrPayClient extends AbstractAlipayClient { |
| 32 | 22 | ||
| 33 | public AlipayQrPayClient(Long channelId, AlipayPayClientConfig config) { | 23 | public AlipayQrPayClient(Long channelId, AlipayPayClientConfig config) { |
| 34 | super(channelId, PayChannelEnum.ALIPAY_QR.getCode(), config, new AlipayPayCodeMapping()); | 24 | super(channelId, PayChannelEnum.ALIPAY_QR.getCode(), config, new AlipayPayCodeMapping()); |
| 35 | } | 25 | } |
| 36 | 26 | ||
| 37 | @Override | 27 | @Override |
| 38 | - @SneakyThrows | ||
| 39 | - protected void doInit() { | ||
| 40 | - AlipayConfig alipayConfig = new AlipayConfig(); | ||
| 41 | - BeanUtil.copyProperties(config, alipayConfig, false); | ||
| 42 | - // 真实客户端 | ||
| 43 | - this.client = new DefaultAlipayClient(alipayConfig); | ||
| 44 | - } | ||
| 45 | - | ||
| 46 | - @Override | ||
| 47 | public PayCommonResult<AlipayTradePrecreateResponse> doUnifiedOrder(PayOrderUnifiedReqDTO reqDTO) { | 28 | public PayCommonResult<AlipayTradePrecreateResponse> doUnifiedOrder(PayOrderUnifiedReqDTO reqDTO) { |
| 48 | // 构建 AlipayTradePrecreateModel 请求 | 29 | // 构建 AlipayTradePrecreateModel 请求 |
| 49 | AlipayTradePrecreateModel model = new AlipayTradePrecreateModel(); | 30 | AlipayTradePrecreateModel model = new AlipayTradePrecreateModel(); |
| @@ -56,7 +37,7 @@ public class AlipayQrPayClient extends AbstractPayClient<AlipayPayClientConfig> | @@ -56,7 +37,7 @@ public class AlipayQrPayClient extends AbstractPayClient<AlipayPayClientConfig> | ||
| 56 | AlipayTradePrecreateRequest request = new AlipayTradePrecreateRequest(); | 37 | AlipayTradePrecreateRequest request = new AlipayTradePrecreateRequest(); |
| 57 | request.setBizModel(model); | 38 | request.setBizModel(model); |
| 58 | request.setNotifyUrl(reqDTO.getNotifyUrl()); | 39 | request.setNotifyUrl(reqDTO.getNotifyUrl()); |
| 59 | - | 40 | + request.setReturnUrl(reqDTO.getReturnUrl()); |
| 60 | // 执行请求 | 41 | // 执行请求 |
| 61 | AlipayTradePrecreateResponse response; | 42 | AlipayTradePrecreateResponse response; |
| 62 | try { | 43 | try { |
| @@ -68,30 +49,4 @@ public class AlipayQrPayClient extends AbstractPayClient<AlipayPayClientConfig> | @@ -68,30 +49,4 @@ public class AlipayQrPayClient extends AbstractPayClient<AlipayPayClientConfig> | ||
| 68 | // TODO 芋艿:sub Code 需要测试下各种失败的情况 | 49 | // TODO 芋艿:sub Code 需要测试下各种失败的情况 |
| 69 | return PayCommonResult.build(response.getCode(), response.getMsg(), response, codeMapping); | 50 | return PayCommonResult.build(response.getCode(), response.getMsg(), response, codeMapping); |
| 70 | } | 51 | } |
| 71 | - | ||
| 72 | - | ||
| 73 | - | ||
| 74 | - @Override | ||
| 75 | - public PayOrderNotifyRespDTO parseOrderNotify(PayNotifyDataDTO data) throws Exception { | ||
| 76 | - //结果转换 | ||
| 77 | - Map<String, String> params = data.getParams(); | ||
| 78 | - return PayOrderNotifyRespDTO.builder().orderExtensionNo(params.get("out_trade_no")) | ||
| 79 | - .channelOrderNo(params.get("trade_no")).channelUserId(params.get("seller_id")) | ||
| 80 | - .tradeStatus(params.get("trade_status")) | ||
| 81 | - .successTime(DateUtil.parse(params.get("notify_time"), "yyyy-MM-dd HH:mm:ss")) | ||
| 82 | - .data(data.getBody()).build(); | ||
| 83 | - | ||
| 84 | - } | ||
| 85 | - | ||
| 86 | - @Override | ||
| 87 | - public PayRefundNotifyDTO parseRefundNotify(PayNotifyDataDTO notifyData) { | ||
| 88 | - //TODO 需要实现 | ||
| 89 | - throw new UnsupportedOperationException("需要实现"); | ||
| 90 | - } | ||
| 91 | - | ||
| 92 | - @Override | ||
| 93 | - protected PayRefundUnifiedRespDTO doUnifiedRefund(PayRefundUnifiedReqDTO reqDTO) throws Throwable { | ||
| 94 | - //TODO 需要实现 | ||
| 95 | - throw new UnsupportedOperationException(); | ||
| 96 | - } | ||
| 97 | } | 52 | } |
yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/alipay/AlipayWapPayClient.java
| 1 | package cn.iocoder.yudao.framework.pay.core.client.impl.alipay; | 1 | package cn.iocoder.yudao.framework.pay.core.client.impl.alipay; |
| 2 | 2 | ||
| 3 | -import cn.hutool.core.bean.BeanUtil; | ||
| 4 | import cn.hutool.core.date.DateUtil; | 3 | import cn.hutool.core.date.DateUtil; |
| 5 | import cn.iocoder.yudao.framework.pay.core.client.PayCommonResult; | 4 | import cn.iocoder.yudao.framework.pay.core.client.PayCommonResult; |
| 6 | -import cn.iocoder.yudao.framework.pay.core.client.dto.*; | ||
| 7 | -import cn.iocoder.yudao.framework.pay.core.client.impl.AbstractPayClient; | 5 | +import cn.iocoder.yudao.framework.pay.core.client.dto.PayOrderUnifiedReqDTO; |
| 8 | import cn.iocoder.yudao.framework.pay.core.enums.PayChannelEnum; | 6 | import cn.iocoder.yudao.framework.pay.core.enums.PayChannelEnum; |
| 9 | -import cn.iocoder.yudao.framework.pay.core.enums.PayNotifyRefundStatusEnum; | ||
| 10 | -import cn.iocoder.yudao.framework.pay.core.enums.PayChannelRespEnum; | ||
| 11 | import com.alipay.api.AlipayApiException; | 7 | import com.alipay.api.AlipayApiException; |
| 12 | -import com.alipay.api.AlipayConfig; | ||
| 13 | -import com.alipay.api.DefaultAlipayClient; | ||
| 14 | -import com.alipay.api.domain.AlipayTradeRefundModel; | ||
| 15 | import com.alipay.api.domain.AlipayTradeWapPayModel; | 8 | import com.alipay.api.domain.AlipayTradeWapPayModel; |
| 16 | -import com.alipay.api.request.AlipayTradeRefundRequest; | ||
| 17 | import com.alipay.api.request.AlipayTradeWapPayRequest; | 9 | import com.alipay.api.request.AlipayTradeWapPayRequest; |
| 18 | -import com.alipay.api.response.AlipayTradeRefundResponse; | ||
| 19 | import com.alipay.api.response.AlipayTradeWapPayResponse; | 10 | import com.alipay.api.response.AlipayTradeWapPayResponse; |
| 20 | -import lombok.SneakyThrows; | ||
| 21 | import lombok.extern.slf4j.Slf4j; | 11 | import lombok.extern.slf4j.Slf4j; |
| 22 | 12 | ||
| 23 | -import java.net.SocketTimeoutException; | ||
| 24 | -import java.util.Map; | ||
| 25 | import java.util.Objects; | 13 | import java.util.Objects; |
| 26 | 14 | ||
| 27 | -import static cn.iocoder.yudao.framework.common.util.json.JsonUtils.toJsonString; | ||
| 28 | - | ||
| 29 | /** | 15 | /** |
| 30 | * 支付宝【手机网站】的 PayClient 实现类 | 16 | * 支付宝【手机网站】的 PayClient 实现类 |
| 31 | * 文档:https://opendocs.alipay.com/apis/api_1/alipay.trade.wap.pay | 17 | * 文档:https://opendocs.alipay.com/apis/api_1/alipay.trade.wap.pay |
| @@ -33,23 +19,14 @@ import static cn.iocoder.yudao.framework.common.util.json.JsonUtils.toJsonString | @@ -33,23 +19,14 @@ import static cn.iocoder.yudao.framework.common.util.json.JsonUtils.toJsonString | ||
| 33 | * @author 芋道源码 | 19 | * @author 芋道源码 |
| 34 | */ | 20 | */ |
| 35 | @Slf4j | 21 | @Slf4j |
| 36 | -public class AlipayWapPayClient extends AbstractPayClient<AlipayPayClientConfig> { | 22 | +public class AlipayWapPayClient extends AbstractAlipayClient { |
| 37 | 23 | ||
| 38 | - private DefaultAlipayClient client; | ||
| 39 | 24 | ||
| 40 | public AlipayWapPayClient(Long channelId, AlipayPayClientConfig config) { | 25 | public AlipayWapPayClient(Long channelId, AlipayPayClientConfig config) { |
| 41 | super(channelId, PayChannelEnum.ALIPAY_WAP.getCode(), config, new AlipayPayCodeMapping()); | 26 | super(channelId, PayChannelEnum.ALIPAY_WAP.getCode(), config, new AlipayPayCodeMapping()); |
| 42 | } | 27 | } |
| 43 | 28 | ||
| 44 | @Override | 29 | @Override |
| 45 | - @SneakyThrows | ||
| 46 | - protected void doInit() { | ||
| 47 | - AlipayConfig alipayConfig = new AlipayConfig(); | ||
| 48 | - BeanUtil.copyProperties(config, alipayConfig, false); | ||
| 49 | - this.client = new DefaultAlipayClient(alipayConfig); | ||
| 50 | - } | ||
| 51 | - | ||
| 52 | - @Override | ||
| 53 | public PayCommonResult<AlipayTradeWapPayResponse> doUnifiedOrder(PayOrderUnifiedReqDTO reqDTO) { | 30 | public PayCommonResult<AlipayTradeWapPayResponse> doUnifiedOrder(PayOrderUnifiedReqDTO reqDTO) { |
| 54 | // 构建 AlipayTradeWapPayModel 请求 | 31 | // 构建 AlipayTradeWapPayModel 请求 |
| 55 | AlipayTradeWapPayModel model = new AlipayTradeWapPayModel(); | 32 | AlipayTradeWapPayModel model = new AlipayTradeWapPayModel(); |
| @@ -69,6 +46,7 @@ public class AlipayWapPayClient extends AbstractPayClient<AlipayPayClientConfig> | @@ -69,6 +46,7 @@ public class AlipayWapPayClient extends AbstractPayClient<AlipayPayClientConfig> | ||
| 69 | request.setBizModel(model); | 46 | request.setBizModel(model); |
| 70 | request.setNotifyUrl(reqDTO.getNotifyUrl()); | 47 | request.setNotifyUrl(reqDTO.getNotifyUrl()); |
| 71 | request.setReturnUrl(reqDTO.getReturnUrl()); | 48 | request.setReturnUrl(reqDTO.getReturnUrl()); |
| 49 | + | ||
| 72 | // 执行请求 | 50 | // 执行请求 |
| 73 | AlipayTradeWapPayResponse response; | 51 | AlipayTradeWapPayResponse response; |
| 74 | try { | 52 | try { |
| @@ -87,85 +65,11 @@ public class AlipayWapPayClient extends AbstractPayClient<AlipayPayClientConfig> | @@ -87,85 +65,11 @@ public class AlipayWapPayClient extends AbstractPayClient<AlipayPayClientConfig> | ||
| 87 | } | 65 | } |
| 88 | 66 | ||
| 89 | 67 | ||
| 90 | - /** | ||
| 91 | - * 从支付宝通知返回参数中解析 PayOrderNotifyRespDTO, 通知具体参数参考 | ||
| 92 | - * //https://opendocs.alipay.com/open/203/105286 | ||
| 93 | - * @param data 通知结果 | ||
| 94 | - * @return 解析结果 PayOrderNotifyRespDTO | ||
| 95 | - * @throws Exception 解析失败,抛出异常 | ||
| 96 | - */ | ||
| 97 | - @Override | ||
| 98 | - public PayOrderNotifyRespDTO parseOrderNotify(PayNotifyDataDTO data) throws Exception { | ||
| 99 | - Map<String, String> params = data.getParams(); | ||
| 100 | - return PayOrderNotifyRespDTO.builder().orderExtensionNo(params.get("out_trade_no")) | ||
| 101 | - .channelOrderNo(params.get("trade_no")).channelUserId(params.get("seller_id")) | ||
| 102 | - .tradeStatus(params.get("trade_status")) | ||
| 103 | - .successTime(DateUtil.parse(params.get("notify_time"), "yyyy-MM-dd HH:mm:ss")) | ||
| 104 | - .data(data.getBody()).build(); | ||
| 105 | - } | ||
| 106 | 68 | ||
| 107 | - @Override | ||
| 108 | - protected PayRefundUnifiedRespDTO doUnifiedRefund(PayRefundUnifiedReqDTO reqDTO) { | ||
| 109 | - AlipayTradeRefundModel model=new AlipayTradeRefundModel(); | ||
| 110 | - model.setTradeNo(reqDTO.getChannelOrderNo()); | ||
| 111 | - model.setOutTradeNo(reqDTO.getPayTradeNo()); | ||
| 112 | - model.setOutRequestNo(reqDTO.getRefundReqNo()); | ||
| 113 | - model.setRefundAmount(calculateAmount(reqDTO.getAmount()).toString()); | ||
| 114 | - model.setRefundReason(reqDTO.getReason()); | ||
| 115 | - AlipayTradeRefundRequest refundRequest = new AlipayTradeRefundRequest(); | ||
| 116 | - refundRequest.setBizModel(model); | ||
| 117 | - PayRefundUnifiedRespDTO respDTO = new PayRefundUnifiedRespDTO(); | ||
| 118 | - try { | ||
| 119 | - AlipayTradeRefundResponse response = client.execute(refundRequest); | ||
| 120 | - log.info("[doUnifiedRefund][response({}) 发起退款 渠道返回", toJsonString(response)); | ||
| 121 | - if (response.isSuccess()) { | ||
| 122 | - //退款成功,更新为PROCESSING_NOTIFY, 而不是 SYNC_SUCCESS 通过支付宝回调接口处理。退款导致触发的异步通知, | ||
| 123 | - //退款导致触发的异步通知是发送到支付接口中设置的notify_url | ||
| 124 | - //TODO 沙箱环境 返回 的tradeNo(渠道退款单号) 和 订单的tradNo 是一个值,是不是理解不对? | ||
| 125 | - respDTO.setRespEnum(PayChannelRespEnum.PROCESSING_NOTIFY); | ||
| 126 | - }else{ | ||
| 127 | - //特殊处理 sub_code ACQ.SYSTEM_ERROR(系统错误), 需要调用重试任务 | ||
| 128 | - //沙箱环境返回的貌似是”aop.ACQ.SYSTEM_ERROR“, 用contain | ||
| 129 | - if (response.getSubCode().contains("ACQ.SYSTEM_ERROR")) { | ||
| 130 | - respDTO.setRespEnum(PayChannelRespEnum.RETRY_FAILURE) | ||
| 131 | - .setChannelErrMsg(response.getSubMsg()) | ||
| 132 | - .setChannelErrCode(response.getSubCode()); | ||
| 133 | - }else{ | ||
| 134 | - //其他当做不可以重试的错误 | ||
| 135 | - respDTO.setRespEnum(PayChannelRespEnum.CAN_NOT_RETRY_FAILURE) | ||
| 136 | - .setChannelErrCode(response.getSubCode()) | ||
| 137 | - .setChannelErrMsg(response.getSubMsg()); | ||
| 138 | - } | ||
| 139 | - } | ||
| 140 | - return respDTO; | ||
| 141 | - } catch (AlipayApiException e) { | ||
| 142 | - //TODO 记录异常日志 | ||
| 143 | - log.error("[doUnifiedRefund][request({}) 发起退款失败,网络读超时,退款状态未知]", toJsonString(reqDTO), e); | ||
| 144 | - Throwable cause = e.getCause(); | ||
| 145 | - //网络 read time out 异常, 退款状态未知 | ||
| 146 | - if (cause instanceof SocketTimeoutException) { | ||
| 147 | - respDTO.setExceptionMsg(e.getMessage()) | ||
| 148 | - .setRespEnum(PayChannelRespEnum.READ_TIME_OUT_EXCEPTION); | ||
| 149 | - }else{ | ||
| 150 | - respDTO.setExceptionMsg(e.getMessage()) | ||
| 151 | - .setChannelErrCode(e.getErrCode()) | ||
| 152 | - .setChannelErrMsg(e.getErrMsg()) | ||
| 153 | - .setRespEnum(PayChannelRespEnum.CALL_EXCEPTION); | ||
| 154 | - } | ||
| 155 | - return respDTO; | ||
| 156 | - } | ||
| 157 | - } | ||
| 158 | 69 | ||
| 159 | - @Override | ||
| 160 | - public PayRefundNotifyDTO parseRefundNotify(PayNotifyDataDTO notifyData) { | ||
| 161 | - Map<String, String> params = notifyData.getParams(); | ||
| 162 | - PayRefundNotifyDTO notifyDTO = PayRefundNotifyDTO.builder().channelOrderNo(params.get("trade_no")) | ||
| 163 | - .tradeNo(params.get("out_trade_no")) | ||
| 164 | - .reqNo(params.get("out_biz_no")) | ||
| 165 | - .status(PayNotifyRefundStatusEnum.SUCCESS) | ||
| 166 | - .refundSuccessTime(DateUtil.parse(params.get("gmt_refund"), "yyyy-MM-dd HH:mm:ss")) | ||
| 167 | - .build(); | ||
| 168 | - return notifyDTO; | ||
| 169 | - } | 70 | + |
| 71 | + | ||
| 72 | + | ||
| 73 | + | ||
| 170 | 74 | ||
| 171 | } | 75 | } |
yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/UserServerApplication.java
| @@ -4,7 +4,8 @@ import org.springframework.boot.SpringApplication; | @@ -4,7 +4,8 @@ import org.springframework.boot.SpringApplication; | ||
| 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; | 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; |
| 5 | 5 | ||
| 6 | @SuppressWarnings("SpringComponentScan") // 忽略 IDEA 无法识别 ${yudao.info.base-package} 和 ${yudao.core-service.base-package} | 6 | @SuppressWarnings("SpringComponentScan") // 忽略 IDEA 无法识别 ${yudao.info.base-package} 和 ${yudao.core-service.base-package} |
| 7 | -@SpringBootApplication(scanBasePackages = {"${yudao.info.base-package}", "${yudao.core-service.base-package}"})public class UserServerApplication { | 7 | +@SpringBootApplication(scanBasePackages = {"${yudao.info.base-package}", "${yudao.core-service.base-package}"}) |
| 8 | +public class UserServerApplication { | ||
| 8 | 9 | ||
| 9 | public static void main(String[] args) { | 10 | public static void main(String[] args) { |
| 10 | SpringApplication.run(UserServerApplication.class, args); | 11 | SpringApplication.run(UserServerApplication.class, args); |
yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/modules/pay/controller/order/PayOrderController.java
| @@ -2,6 +2,7 @@ package cn.iocoder.yudao.userserver.modules.pay.controller.order; | @@ -2,6 +2,7 @@ package cn.iocoder.yudao.userserver.modules.pay.controller.order; | ||
| 2 | 2 | ||
| 3 | import cn.hutool.core.bean.BeanUtil; | 3 | import cn.hutool.core.bean.BeanUtil; |
| 4 | import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.order.PayOrderDO; | 4 | import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.order.PayOrderDO; |
| 5 | +import cn.iocoder.yudao.coreservice.modules.pay.service.order.PayCommonCoreService; | ||
| 5 | import cn.iocoder.yudao.coreservice.modules.pay.service.order.PayOrderCoreService; | 6 | import cn.iocoder.yudao.coreservice.modules.pay.service.order.PayOrderCoreService; |
| 6 | import cn.iocoder.yudao.coreservice.modules.pay.service.order.PayRefundCoreService; | 7 | import cn.iocoder.yudao.coreservice.modules.pay.service.order.PayRefundCoreService; |
| 7 | import cn.iocoder.yudao.coreservice.modules.pay.service.order.dto.PayOrderSubmitReqDTO; | 8 | import cn.iocoder.yudao.coreservice.modules.pay.service.order.dto.PayOrderSubmitReqDTO; |
| @@ -38,6 +39,9 @@ public class PayOrderController { | @@ -38,6 +39,9 @@ public class PayOrderController { | ||
| 38 | @Resource | 39 | @Resource |
| 39 | private PayRefundCoreService payRefundCoreService; | 40 | private PayRefundCoreService payRefundCoreService; |
| 40 | 41 | ||
| 42 | + @Resource PayCommonCoreService commonCoreService; | ||
| 43 | + | ||
| 44 | + | ||
| 41 | @PostMapping("/submit") | 45 | @PostMapping("/submit") |
| 42 | @ApiOperation("提交支付订单") | 46 | @ApiOperation("提交支付订单") |
| 43 | // @PreAuthenticated // TODO 暂时不加登陆验证,前端暂时没做好 | 47 | // @PreAuthenticated // TODO 暂时不加登陆验证,前端暂时没做好 |
| @@ -57,82 +61,53 @@ public class PayOrderController { | @@ -57,82 +61,53 @@ public class PayOrderController { | ||
| 57 | } | 61 | } |
| 58 | 62 | ||
| 59 | // ========== 支付渠道的回调 ========== | 63 | // ========== 支付渠道的回调 ========== |
| 60 | - | 64 | + //TODO 芋道源码 换成了统一的地址了 /notify/{channelId},测试通过可以删除 |
| 61 | @PostMapping("/notify/wx-pub/{channelId}") | 65 | @PostMapping("/notify/wx-pub/{channelId}") |
| 62 | @ApiOperation("通知微信公众号支付的结果") | 66 | @ApiOperation("通知微信公众号支付的结果") |
| 63 | public String notifyWxPayOrder(@PathVariable("channelId") Long channelId, | 67 | public String notifyWxPayOrder(@PathVariable("channelId") Long channelId, |
| 64 | @RequestBody String xmlData) throws Exception { | 68 | @RequestBody String xmlData) throws Exception { |
| 65 | - payOrderCoreService.notifyPayOrder(channelId, PayChannelEnum.WX_PUB.getCode(), PayNotifyDataDTO.builder().body(xmlData).build()); | 69 | + payOrderCoreService.notifyPayOrder(channelId, PayNotifyDataDTO.builder().body(xmlData).build()); |
| 66 | return "success"; | 70 | return "success"; |
| 67 | } | 71 | } |
| 68 | 72 | ||
| 69 | - @PostMapping("/notify/alipay-qr/{channelId}") | ||
| 70 | - @ApiOperation("通知支付宝扫码支付的结果") | ||
| 71 | - public String notifyAlipayQrPayOrder(@PathVariable("channelId") Long channelId, | ||
| 72 | - @RequestParam Map<String, String> params, | ||
| 73 | - @RequestBody String originData) throws Exception{ | ||
| 74 | - payOrderCoreService.notifyPayOrder(channelId, PayChannelEnum.ALIPAY_QR.getCode(), | ||
| 75 | - PayNotifyDataDTO.builder().params(params).body(originData).build()); | ||
| 76 | - return "success"; | ||
| 77 | - } | ||
| 78 | - | ||
| 79 | - @GetMapping(value = "/return/alipay-qr/{channelId}") | ||
| 80 | - @ApiOperation("支付宝 wap 页面回跳") | ||
| 81 | - public String returnAliPayQrPayOrder(@PathVariable("channelId") Long channelId){ | ||
| 82 | - //TODO @jason 校验 是否支付宝调用。 支付宝publickey 可以根据 appId 跳转不同的页面 | ||
| 83 | - System.out.println("支付成功"); | ||
| 84 | - return "支付成功"; | ||
| 85 | - } | ||
| 86 | - | ||
| 87 | - @PostMapping(value = "/notify/alipay-wap/{channelId}", consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE) | ||
| 88 | - @ApiOperation("支付宝 wap 页面回调") | ||
| 89 | - public String notifyAliPayWapPayOrder(@PathVariable("channelId") Long channelId, | ||
| 90 | - @RequestParam Map<String, String> params, | ||
| 91 | - @RequestBody String originData) throws Exception { | ||
| 92 | - //TODO 校验是否支付宝调用。 payclient 中加一个校验方法 | ||
| 93 | - //支付宝退款交易也会触发支付回调接口 | ||
| 94 | - //参考 https://opensupport.alipay.com/support/helpcenter/193/201602484851 | ||
| 95 | - //判断是否为支付宝的退款交易 | ||
| 96 | - if(isAliPayRefund(params)) { | ||
| 97 | - //退款通知 | ||
| 98 | - payRefundCoreService.notifyPayRefund(channelId,PayChannelEnum.ALIPAY_WAP.getCode(), PayNotifyDataDTO.builder().params(params).body(originData).build()); | ||
| 99 | - }else{ | ||
| 100 | - //支付通知 | ||
| 101 | - payOrderCoreService.notifyPayOrder(channelId, PayChannelEnum.ALIPAY_WAP.getCode(), PayNotifyDataDTO.builder().params(params).body(originData).build()); | ||
| 102 | - } | ||
| 103 | - return "success"; | ||
| 104 | - } | ||
| 105 | - | ||
| 106 | - | ||
| 107 | /** | 73 | /** |
| 74 | + * 统一的跳转页面, 支付宝跳转参数说明 | ||
| 108 | * https://opendocs.alipay.com/open/203/105285#%E5%89%8D%E5%8F%B0%E5%9B%9E%E8%B7%B3%E5%8F%82%E6%95%B0%E8%AF%B4%E6%98%8E | 75 | * https://opendocs.alipay.com/open/203/105285#%E5%89%8D%E5%8F%B0%E5%9B%9E%E8%B7%B3%E5%8F%82%E6%95%B0%E8%AF%B4%E6%98%8E |
| 109 | * @param channelId 渠道id | 76 | * @param channelId 渠道id |
| 110 | * @return 返回跳转页面 | 77 | * @return 返回跳转页面 |
| 111 | */ | 78 | */ |
| 112 | - @GetMapping(value = "/return/alipay-wap/{channelId}") | ||
| 113 | - @ApiOperation("支付宝 wap 页面回跳") | ||
| 114 | - public String returnAliPayWapPayOrder(@PathVariable("channelId") Long channelId){ | ||
| 115 | - //TODO 校验 是否支付宝调用。 可以根据 appId 跳转不同的页面 | ||
| 116 | - return "支付成功"; | 79 | + @GetMapping(value = "/return/{channelId}") |
| 80 | + @ApiOperation("渠道统一的支付成功返回地址") | ||
| 81 | + public String returnAliPayOrder(@PathVariable("channelId") Long channelId, @RequestParam Map<String, String> params){ | ||
| 82 | + //TODO 可以根据渠道和 app_id 返回不同的页面 | ||
| 83 | + log.info("app_id is {}", params.get("app_id")); | ||
| 84 | + return String.format("渠道[%s]支付成功", String.valueOf(channelId)); | ||
| 117 | } | 85 | } |
| 118 | 86 | ||
| 119 | /** | 87 | /** |
| 120 | - * 是否是支付宝的退款交易 | ||
| 121 | - * @param params http content-type application/x-www-form-urlencoded 的参数 | ||
| 122 | - * @return | 88 | + * 统一的渠道支付回调,支付宝的退款回调 |
| 89 | + * @param channelId 渠道编号 | ||
| 90 | + * @param params form 参数 | ||
| 91 | + * @param originData http request body | ||
| 92 | + * @return 成功返回 "success" | ||
| 123 | */ | 93 | */ |
| 124 | - private boolean isAliPayRefund(Map<String, String> params) { | ||
| 125 | - if (params.containsKey("refund_fee")) { | ||
| 126 | - return true; | ||
| 127 | - } else { | ||
| 128 | - return false; | 94 | + @PostMapping(value = "/notify/{channelId}") |
| 95 | + @ApiOperation("渠道统一的支付成功,或退款成功 通知url") | ||
| 96 | + public String notifyChannelPay(@PathVariable("channelId") Long channelId, | ||
| 97 | + @RequestParam Map<String, String> params, | ||
| 98 | + @RequestBody String originData) throws Exception { | ||
| 99 | + //校验是否是渠道回调 | ||
| 100 | + commonCoreService.verifyNotifyData(channelId, PayNotifyDataDTO.builder().params(params).body(originData).build()); | ||
| 101 | + //支付宝退款交易也会触发支付回调接口 | ||
| 102 | + //参考 https://opensupport.alipay.com/support/helpcenter/193/201602484851 | ||
| 103 | + //判断是否为退款通知 | ||
| 104 | + if(commonCoreService.isRefundNotify(channelId, PayNotifyDataDTO.builder().params(params).body(originData).build())) { | ||
| 105 | + //退款通知 | ||
| 106 | + payRefundCoreService.notifyPayRefund(channelId,PayNotifyDataDTO.builder().params(params).body(originData).build()); | ||
| 107 | + }else{ | ||
| 108 | + //支付通知 | ||
| 109 | + payOrderCoreService.notifyPayOrder(channelId,PayNotifyDataDTO.builder().params(params).body(originData).build()); | ||
| 129 | } | 110 | } |
| 130 | - } | ||
| 131 | - | ||
| 132 | - @RequestMapping("/notify/test") | ||
| 133 | - @ApiOperation("通知的测试接口") | ||
| 134 | - public String notifyTest() { | ||
| 135 | -// System.out.println(data); | ||
| 136 | return "success"; | 111 | return "success"; |
| 137 | } | 112 | } |
| 138 | 113 |