!417 支付收银台,接入支付宝的 PC、Wap、二维码、条码、App 等支付方式
Merge pull request !417 from 芋道源码/feature/dev-yunai
|
@ -1,33 +0,0 @@
|
|||
package cn.iocoder.yudao.framework.pay.core.client;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.exception.ErrorCode;
|
||||
import cn.iocoder.yudao.framework.pay.core.enums.PayFrameworkErrorCodeConstants;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
/**
|
||||
* 将 API 的错误码,转换为通用的错误码
|
||||
*
|
||||
* @see PayCommonResult
|
||||
* @see PayFrameworkErrorCodeConstants
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Slf4j
|
||||
public abstract class AbstractPayCodeMapping {
|
||||
|
||||
public final ErrorCode apply(String apiCode, String apiMsg) {
|
||||
if (apiCode == null) {
|
||||
log.error("[apply][API 错误码为空,请排查]");
|
||||
return PayFrameworkErrorCodeConstants.EXCEPTION;
|
||||
}
|
||||
ErrorCode errorCode = this.apply0(apiCode, apiMsg);
|
||||
if (errorCode == null) {
|
||||
log.error("[apply][API 错误码({}) 错误提示({}) 无法匹配]", apiCode, apiMsg);
|
||||
return PayFrameworkErrorCodeConstants.PAY_UNKNOWN;
|
||||
}
|
||||
return errorCode;
|
||||
}
|
||||
|
||||
protected abstract ErrorCode apply0(String apiCode, String apiMsg);
|
||||
|
||||
}
|
|
@ -1,7 +1,12 @@
|
|||
package cn.iocoder.yudao.framework.pay.core.client;
|
||||
|
||||
|
||||
import cn.iocoder.yudao.framework.pay.core.client.dto.*;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.dto.notify.PayNotifyReqDTO;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.dto.notify.PayOrderNotifyRespDTO;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.dto.notify.PayRefundNotifyRespDTO;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderUnifiedReqDTO;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderUnifiedRespDTO;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.dto.refund.PayRefundUnifiedReqDTO;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.dto.refund.PayRefundUnifiedRespDTO;
|
||||
|
||||
/**
|
||||
* 支付客户端,用于对接各支付渠道的 SDK,实现发起支付、退款等功能
|
||||
|
@ -23,51 +28,25 @@ public interface PayClient {
|
|||
* @param reqDTO 下单信息
|
||||
* @return 各支付渠道的返回结果
|
||||
*/
|
||||
PayCommonResult<?> unifiedOrder(PayOrderUnifiedReqDTO reqDTO);
|
||||
|
||||
/**
|
||||
* 解析支付单的通知结果
|
||||
*
|
||||
* @param data 通知结果
|
||||
* @return 解析结果
|
||||
* @throws Exception 解析失败,抛出异常
|
||||
*/
|
||||
PayOrderNotifyRespDTO parseOrderNotify(PayNotifyDataDTO data) throws Exception;
|
||||
PayOrderUnifiedRespDTO unifiedOrder(PayOrderUnifiedReqDTO reqDTO);
|
||||
|
||||
/**
|
||||
* 调用支付渠道,进行退款
|
||||
* @param reqDTO 统一退款请求信息
|
||||
* @return 各支付渠道的统一返回结果
|
||||
*/
|
||||
PayCommonResult<PayRefundUnifiedRespDTO> unifiedRefund(PayRefundUnifiedReqDTO reqDTO);
|
||||
PayRefundUnifiedRespDTO unifiedRefund(PayRefundUnifiedReqDTO reqDTO);
|
||||
|
||||
/**
|
||||
* 解析支付退款通知数据
|
||||
* @param notifyData 支付退款通知请求数据
|
||||
* @return 支付退款通知的Notify DTO
|
||||
*/
|
||||
PayRefundNotifyDTO parseRefundNotify(PayNotifyDataDTO notifyData);
|
||||
|
||||
// TODO @芋艿:后续改成非 default,避免不知道去实现
|
||||
/**
|
||||
* 验证是否渠道通知
|
||||
* 解析回调数据
|
||||
*
|
||||
* @param notifyData 通知数据
|
||||
* @return 默认是 true
|
||||
* @param rawNotify 通知内容
|
||||
* @return 回调对象
|
||||
* 1. {@link PayRefundNotifyRespDTO} 退款通知
|
||||
* 2. {@link PayOrderNotifyRespDTO} 支付通知
|
||||
*/
|
||||
default boolean verifyNotifyData(PayNotifyDataDTO notifyData) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// TODO @芋艿:后续改成非 default,避免不知道去实现
|
||||
/**
|
||||
* 判断是否为退款通知
|
||||
*
|
||||
* @param notifyData 通知数据
|
||||
* @return 默认是 false
|
||||
*/
|
||||
default boolean isRefundNotify(PayNotifyDataDTO notifyData){
|
||||
return false;
|
||||
default Object parseNotify(PayNotifyReqDTO rawNotify) {
|
||||
throw new UnsupportedOperationException("未实现 parseNotify 方法!");
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,57 +0,0 @@
|
|||
package cn.iocoder.yudao.framework.pay.core.client;
|
||||
|
||||
import cn.hutool.core.exceptions.ExceptionUtil;
|
||||
import cn.hutool.core.lang.Assert;
|
||||
import cn.iocoder.yudao.framework.common.exception.ErrorCode;
|
||||
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
|
||||
import cn.iocoder.yudao.framework.pay.core.enums.PayFrameworkErrorCodeConstants;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.ToString;
|
||||
|
||||
/**
|
||||
* 支付的 CommonResult 拓展类
|
||||
*
|
||||
* 考虑到不同的平台,返回的 code 和 msg 是不同的,所以统一额外返回 {@link #apiCode} 和 {@link #apiMsg} 字段
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@ToString(callSuper = true)
|
||||
public class PayCommonResult<T> extends CommonResult<T> {
|
||||
|
||||
/**
|
||||
* API 返回错误码
|
||||
*
|
||||
* 由于第三方的错误码可能是字符串,所以使用 String 类型
|
||||
*/
|
||||
private String apiCode;
|
||||
/**
|
||||
* API 返回提示
|
||||
*/
|
||||
private String apiMsg;
|
||||
|
||||
private PayCommonResult() {
|
||||
}
|
||||
|
||||
public static <T> PayCommonResult<T> build(String apiCode, String apiMsg, T data, AbstractPayCodeMapping codeMapping) {
|
||||
Assert.notNull(codeMapping, "参数 codeMapping 不能为空");
|
||||
PayCommonResult<T> result = new PayCommonResult<T>().setApiCode(apiCode).setApiMsg(apiMsg);
|
||||
result.setData(data);
|
||||
// 翻译错误码
|
||||
if (codeMapping != null) {
|
||||
ErrorCode errorCode = codeMapping.apply(apiCode, apiMsg);
|
||||
result.setCode(errorCode.getCode()).setMsg(errorCode.getMsg());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public static <T> PayCommonResult<T> error(Throwable ex) {
|
||||
PayCommonResult<T> result = new PayCommonResult<>();
|
||||
result.setCode(PayFrameworkErrorCodeConstants.EXCEPTION.getCode());
|
||||
result.setMsg(ExceptionUtil.getRootCauseMessage(ex));
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
package cn.iocoder.yudao.framework.pay.core.client.dto;
|
||||
package cn.iocoder.yudao.framework.pay.core.client.dto.notify;
|
||||
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
|
@ -13,7 +13,7 @@ import java.util.Map;
|
|||
@Data
|
||||
@ToString
|
||||
@Builder
|
||||
public class PayNotifyDataDTO {
|
||||
public class PayNotifyReqDTO {
|
||||
|
||||
|
||||
/**
|
|
@ -1,4 +1,4 @@
|
|||
package cn.iocoder.yudao.framework.pay.core.client.dto;
|
||||
package cn.iocoder.yudao.framework.pay.core.client.dto.notify;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
|
@ -35,15 +35,9 @@ public class PayOrderNotifyRespDTO {
|
|||
*/
|
||||
private LocalDateTime successTime;
|
||||
|
||||
/**
|
||||
* 通知的原始数据
|
||||
*
|
||||
* 主要用于持久化,方便后续修复数据,或者排错
|
||||
*/
|
||||
private String data;
|
||||
|
||||
/**
|
||||
* TODO @jason 结合其他的渠道定义成枚举,
|
||||
*
|
||||
* alipay
|
||||
* TRADE_CLOSED,未付款交易超时关闭,或支付完成后全额退款。
|
||||
* TRADE_SUCCESS, 交易支付成功
|
|
@ -1,4 +1,4 @@
|
|||
package cn.iocoder.yudao.framework.pay.core.client.dto;
|
||||
package cn.iocoder.yudao.framework.pay.core.client.dto.notify;
|
||||
|
||||
import cn.iocoder.yudao.framework.pay.core.enums.PayNotifyRefundStatusEnum;
|
||||
import lombok.Builder;
|
||||
|
@ -15,14 +15,13 @@ import java.time.LocalDateTime;
|
|||
@Data
|
||||
@ToString
|
||||
@Builder
|
||||
public class PayRefundNotifyDTO {
|
||||
public class PayRefundNotifyRespDTO {
|
||||
|
||||
/**
|
||||
* 支付渠道编号
|
||||
*/
|
||||
private String channelOrderNo;
|
||||
|
||||
|
||||
/**
|
||||
* 交易订单号,根据规则生成
|
||||
* 调用支付渠道时,使用该字段作为对接的订单号。
|
||||
|
@ -46,18 +45,14 @@ public class PayRefundNotifyDTO {
|
|||
*/
|
||||
private String reqNo;
|
||||
|
||||
|
||||
/**
|
||||
* 退款是否成功
|
||||
*/
|
||||
private PayNotifyRefundStatusEnum status;
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 退款成功时间
|
||||
*/
|
||||
private LocalDateTime refundSuccessTime;
|
||||
|
||||
|
||||
}
|
|
@ -1,5 +1,6 @@
|
|||
package cn.iocoder.yudao.framework.pay.core.client.dto;
|
||||
package cn.iocoder.yudao.framework.pay.core.client.dto.order;
|
||||
|
||||
import cn.iocoder.yudao.framework.pay.core.enums.PayDisplayModeEnum;
|
||||
import lombok.Data;
|
||||
import org.hibernate.validator.constraints.Length;
|
||||
import org.hibernate.validator.constraints.URL;
|
||||
|
@ -7,6 +8,7 @@ import org.hibernate.validator.constraints.URL;
|
|||
import javax.validation.constraints.DecimalMin;
|
||||
import javax.validation.constraints.NotEmpty;
|
||||
import javax.validation.constraints.NotNull;
|
||||
import java.awt.*;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Map;
|
||||
|
||||
|
@ -78,4 +80,13 @@ public class PayOrderUnifiedReqDTO {
|
|||
*/
|
||||
private Map<String, String> channelExtras;
|
||||
|
||||
/**
|
||||
* 展示模式
|
||||
*
|
||||
* 如果不传递,则每个支付渠道使用默认的方式
|
||||
*
|
||||
* 枚举 {@link PayDisplayModeEnum}
|
||||
*/
|
||||
private String displayMode;
|
||||
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
package cn.iocoder.yudao.framework.pay.core.client.dto.order;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 统一下单 Response DTO
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Data
|
||||
public class PayOrderUnifiedRespDTO {
|
||||
|
||||
/**
|
||||
* 展示模式
|
||||
*/
|
||||
private String displayMode;
|
||||
/**
|
||||
* 展示内容
|
||||
*/
|
||||
private String displayContent;
|
||||
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
package cn.iocoder.yudao.framework.pay.core.client.dto;
|
||||
package cn.iocoder.yudao.framework.pay.core.client.dto.refund;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
|
@ -1,4 +1,4 @@
|
|||
package cn.iocoder.yudao.framework.pay.core.client.dto;
|
||||
package cn.iocoder.yudao.framework.pay.core.client.dto.refund;
|
||||
|
||||
import cn.iocoder.yudao.framework.pay.core.enums.PayChannelRefundRespEnum;
|
||||
import lombok.AllArgsConstructor;
|
|
@ -1,17 +1,23 @@
|
|||
package cn.iocoder.yudao.framework.pay.core.client.impl;
|
||||
|
||||
import cn.iocoder.yudao.framework.pay.core.client.AbstractPayCodeMapping;
|
||||
import cn.hutool.core.date.LocalDateTimeUtil;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.PayClient;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.PayClientConfig;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.PayCommonResult;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.dto.PayOrderUnifiedReqDTO;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.dto.PayRefundUnifiedReqDTO;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.dto.PayRefundUnifiedRespDTO;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderUnifiedReqDTO;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderUnifiedRespDTO;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.dto.refund.PayRefundUnifiedReqDTO;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.dto.refund.PayRefundUnifiedRespDTO;
|
||||
import com.alipay.api.AlipayResponse;import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import javax.validation.Validation;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
import static cn.hutool.core.date.DatePattern.NORM_DATETIME_FORMATTER;
|
||||
import static cn.hutool.core.date.DatePattern.NORM_DATETIME_MS_FORMATTER;
|
||||
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
|
||||
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception0;
|
||||
import static cn.iocoder.yudao.framework.common.util.json.JsonUtils.toJsonString;
|
||||
import static cn.iocoder.yudao.framework.pay.core.enums.PayFrameworkErrorCodeConstants.PAY_EXCEPTION;
|
||||
|
||||
/**
|
||||
* 支付客户端的抽象类,提供模板方法,减少子类的冗余代码
|
||||
|
@ -29,19 +35,14 @@ public abstract class AbstractPayClient<Config extends PayClientConfig> implemen
|
|||
* 渠道编码
|
||||
*/
|
||||
private final String channelCode;
|
||||
/**
|
||||
* 错误码枚举类
|
||||
*/
|
||||
protected AbstractPayCodeMapping codeMapping;
|
||||
/**
|
||||
* 支付配置
|
||||
*/
|
||||
protected Config config;
|
||||
|
||||
public AbstractPayClient(Long channelId, String channelCode, Config config, AbstractPayCodeMapping codeMapping) {
|
||||
public AbstractPayClient(Long channelId, String channelCode, Config config) {
|
||||
this.channelId = channelId;
|
||||
this.channelCode = channelCode;
|
||||
this.codeMapping = codeMapping;
|
||||
this.config = config;
|
||||
}
|
||||
|
||||
|
@ -69,47 +70,72 @@ public abstract class AbstractPayClient<Config extends PayClientConfig> implemen
|
|||
this.init();
|
||||
}
|
||||
|
||||
protected Double calculateAmount(Integer amount) {
|
||||
return amount / 100.0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long getId() {
|
||||
return channelId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final PayCommonResult<?> unifiedOrder(PayOrderUnifiedReqDTO reqDTO) {
|
||||
public final PayOrderUnifiedRespDTO unifiedOrder(PayOrderUnifiedReqDTO reqDTO) {
|
||||
Validation.buildDefaultValidatorFactory().getValidator().validate(reqDTO);
|
||||
// 执行短信发送
|
||||
PayCommonResult<?> result;
|
||||
PayOrderUnifiedRespDTO result;
|
||||
try {
|
||||
result = doUnifiedOrder(reqDTO);
|
||||
} catch (Throwable ex) {
|
||||
// 打印异常日志
|
||||
log.error("[unifiedOrder][request({}) 发起支付失败]", toJsonString(reqDTO), ex);
|
||||
// 封装返回
|
||||
return PayCommonResult.error(ex);
|
||||
throw buildException(ex);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
protected abstract PayCommonResult<?> doUnifiedOrder(PayOrderUnifiedReqDTO reqDTO)
|
||||
protected abstract PayOrderUnifiedRespDTO doUnifiedOrder(PayOrderUnifiedReqDTO reqDTO)
|
||||
throws Throwable;
|
||||
|
||||
@Override
|
||||
public PayCommonResult<PayRefundUnifiedRespDTO> unifiedRefund(PayRefundUnifiedReqDTO reqDTO) {
|
||||
PayCommonResult<PayRefundUnifiedRespDTO> resp;
|
||||
public PayRefundUnifiedRespDTO unifiedRefund(PayRefundUnifiedReqDTO reqDTO) {
|
||||
PayRefundUnifiedRespDTO resp;
|
||||
try {
|
||||
resp = doUnifiedRefund(reqDTO);
|
||||
} catch (Throwable ex) {
|
||||
// 记录异常日志
|
||||
log.error("[unifiedRefund][request({}) 发起退款失败]", toJsonString(reqDTO), ex);
|
||||
resp = PayCommonResult.error(ex);
|
||||
throw buildException(ex);
|
||||
}
|
||||
return resp;
|
||||
}
|
||||
|
||||
protected abstract PayCommonResult<PayRefundUnifiedRespDTO> doUnifiedRefund(PayRefundUnifiedReqDTO reqDTO) throws Throwable;
|
||||
protected abstract PayRefundUnifiedRespDTO doUnifiedRefund(PayRefundUnifiedReqDTO reqDTO) throws Throwable;
|
||||
|
||||
// ========== 各种工具方法 ==========
|
||||
|
||||
private RuntimeException buildException(Throwable ex) {
|
||||
if (ex instanceof RuntimeException) {
|
||||
return (RuntimeException) ex;
|
||||
}
|
||||
throw new RuntimeException(ex);
|
||||
}
|
||||
|
||||
protected void validateSuccess(AlipayResponse response) {
|
||||
if (response.isSuccess()) {
|
||||
return;
|
||||
}
|
||||
throw exception0(PAY_EXCEPTION.getCode(), response.getSubMsg());
|
||||
}
|
||||
|
||||
protected String formatAmount(Integer amount) {
|
||||
return String.valueOf(amount / 100.0);
|
||||
}
|
||||
|
||||
protected String formatTime(LocalDateTime time) {
|
||||
// "yyyy-MM-dd HH:mm:ss"
|
||||
return LocalDateTimeUtil.format(time, NORM_DATETIME_FORMATTER);
|
||||
}
|
||||
|
||||
protected LocalDateTime parseTime(String str) {
|
||||
// "yyyy-MM-dd HH:mm:ss"
|
||||
return LocalDateTimeUtil.parse(str, NORM_DATETIME_FORMATTER);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -4,10 +4,7 @@ import cn.hutool.core.lang.Assert;
|
|||
import cn.iocoder.yudao.framework.pay.core.client.PayClient;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.PayClientConfig;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.PayClientFactory;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.impl.alipay.AlipayPayClientConfig;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.impl.alipay.AlipayPcPayClient;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.impl.alipay.AlipayQrPayClient;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.impl.alipay.AlipayWapPayClient;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.impl.alipay.*;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.impl.wx.WXLitePayClient;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.impl.wx.WXNativePayClient;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.impl.wx.WXPayClientConfig;
|
||||
|
@ -69,8 +66,9 @@ public class PayClientFactoryImpl implements PayClientFactory {
|
|||
case WX_NATIVE: return (AbstractPayClient<Config>) new WXNativePayClient(channelId, (WXPayClientConfig) config);
|
||||
case ALIPAY_WAP: return (AbstractPayClient<Config>) new AlipayWapPayClient(channelId, (AlipayPayClientConfig) config);
|
||||
case ALIPAY_QR: return (AbstractPayClient<Config>) new AlipayQrPayClient(channelId, (AlipayPayClientConfig) config);
|
||||
case ALIPAY_APP: return (AbstractPayClient<Config>) new AlipayQrPayClient(channelId, (AlipayPayClientConfig) config);
|
||||
case ALIPAY_APP: return (AbstractPayClient<Config>) new AlipayAppPayClient(channelId, (AlipayPayClientConfig) config);
|
||||
case ALIPAY_PC: return (AbstractPayClient<Config>) new AlipayPcPayClient(channelId, (AlipayPayClientConfig) config);
|
||||
case ALIPAY_BAR: return (AbstractPayClient<Config>) new AlipayBarPayClient(channelId, (AlipayPayClientConfig) config);
|
||||
}
|
||||
// 创建失败,错误日志 + 抛出异常
|
||||
log.error("[createPayClient][配置({}) 找不到合适的客户端实现]", config);
|
||||
|
|
|
@ -1,10 +1,12 @@
|
|||
package cn.iocoder.yudao.framework.pay.core.client.impl.alipay;
|
||||
|
||||
import cn.hutool.core.bean.BeanUtil;
|
||||
import cn.hutool.core.date.LocalDateTimeUtil;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.AbstractPayCodeMapping;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.PayCommonResult;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.dto.*;
|
||||
import cn.hutool.http.HttpUtil;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.dto.notify.PayNotifyReqDTO;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.dto.notify.PayOrderNotifyRespDTO;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.dto.notify.PayRefundNotifyRespDTO;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.dto.refund.PayRefundUnifiedReqDTO;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.dto.refund.PayRefundUnifiedRespDTO;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.impl.AbstractPayClient;
|
||||
import cn.iocoder.yudao.framework.pay.core.enums.PayNotifyRefundStatusEnum;
|
||||
import com.alipay.api.AlipayApiException;
|
||||
|
@ -18,7 +20,6 @@ import lombok.SneakyThrows;
|
|||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.util.json.JsonUtils.toJsonString;
|
||||
|
@ -33,9 +34,8 @@ public abstract class AbstractAlipayClient extends AbstractPayClient<AlipayPayCl
|
|||
|
||||
protected DefaultAlipayClient client;
|
||||
|
||||
public AbstractAlipayClient(Long channelId, String channelCode,
|
||||
AlipayPayClientConfig config, AbstractPayCodeMapping codeMapping) {
|
||||
super(channelId, channelCode, config, codeMapping);
|
||||
public AbstractAlipayClient(Long channelId, String channelCode, AlipayPayClientConfig config) {
|
||||
super(channelId, channelCode, config);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -46,71 +46,25 @@ public abstract class AbstractAlipayClient extends AbstractPayClient<AlipayPayCl
|
|||
this.client = new DefaultAlipayClient(alipayConfig);
|
||||
}
|
||||
|
||||
/**
|
||||
* 从支付宝通知返回参数中解析 PayOrderNotifyRespDTO, 通知具体参数参考
|
||||
* //https://opendocs.alipay.com/open/203/105286
|
||||
* @param data 通知结果
|
||||
* @return 解析结果 PayOrderNotifyRespDTO
|
||||
* @throws Exception 解析失败,抛出异常
|
||||
*/
|
||||
@Override
|
||||
public PayOrderNotifyRespDTO parseOrderNotify(PayNotifyDataDTO data) throws Exception {
|
||||
Map<String, String> params = strToMap(data.getBody());
|
||||
|
||||
return PayOrderNotifyRespDTO.builder().orderExtensionNo(params.get("out_trade_no"))
|
||||
.channelOrderNo(params.get("trade_no")).channelUserId(params.get("seller_id"))
|
||||
.tradeStatus(params.get("trade_status"))
|
||||
.successTime(LocalDateTimeUtil.parse(params.get("notify_time"), "yyyy-MM-dd HH:mm:ss"))
|
||||
.data(data.getBody()).build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public PayRefundNotifyDTO parseRefundNotify(PayNotifyDataDTO notifyData) {
|
||||
Map<String, String> params = strToMap(notifyData.getBody());
|
||||
PayRefundNotifyDTO notifyDTO = PayRefundNotifyDTO.builder().channelOrderNo(params.get("trade_no"))
|
||||
.tradeNo(params.get("out_trade_no"))
|
||||
.reqNo(params.get("out_biz_no"))
|
||||
.status(PayNotifyRefundStatusEnum.SUCCESS)
|
||||
.refundSuccessTime(LocalDateTimeUtil.parse(params.get("gmt_refund"), "yyyy-MM-dd HH:mm:ss"))
|
||||
.build();
|
||||
return notifyDTO;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRefundNotify(PayNotifyDataDTO notifyData) {
|
||||
if (notifyData.getParams().containsKey("refund_fee")) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean verifyNotifyData(PayNotifyDataDTO notifyData) {
|
||||
boolean verifyResult = false;
|
||||
try {
|
||||
verifyResult = AlipaySignature.rsaCheckV1(notifyData.getParams(), config.getAlipayPublicKey(), StandardCharsets.UTF_8.name(), "RSA2");
|
||||
} catch (AlipayApiException e) {
|
||||
log.error("[AlipayClient verifyNotifyData][(notify param is :{}) 验证失败]", toJsonString(notifyData.getParams()), e);
|
||||
}
|
||||
return verifyResult;
|
||||
}
|
||||
|
||||
/**
|
||||
* 支付宝统一的退款接口 alipay.trade.refund
|
||||
* @param reqDTO 退款请求 request DTO
|
||||
* @return 退款请求 Response
|
||||
*/
|
||||
@Override
|
||||
protected PayCommonResult<PayRefundUnifiedRespDTO> doUnifiedRefund(PayRefundUnifiedReqDTO reqDTO) {
|
||||
protected PayRefundUnifiedRespDTO doUnifiedRefund(PayRefundUnifiedReqDTO reqDTO) {
|
||||
AlipayTradeRefundModel model=new AlipayTradeRefundModel();
|
||||
model.setTradeNo(reqDTO.getChannelOrderNo());
|
||||
model.setOutTradeNo(reqDTO.getPayTradeNo());
|
||||
|
||||
model.setOutRequestNo(reqDTO.getMerchantRefundId());
|
||||
model.setRefundAmount(calculateAmount(reqDTO.getAmount()).toString());
|
||||
model.setRefundAmount(formatAmount(reqDTO.getAmount()).toString());
|
||||
model.setRefundReason(reqDTO.getReason());
|
||||
|
||||
AlipayTradeRefundRequest refundRequest = new AlipayTradeRefundRequest();
|
||||
refundRequest.setBizModel(model);
|
||||
refundRequest.setNotifyUrl(reqDTO.getNotifyUrl());
|
||||
refundRequest.setReturnUrl(reqDTO.getNotifyUrl());
|
||||
try {
|
||||
AlipayTradeRefundResponse response = client.execute(refundRequest);
|
||||
log.info("[doUnifiedRefund][response({}) 发起退款 渠道返回", toJsonString(response));
|
||||
|
@ -119,38 +73,43 @@ public abstract class AbstractAlipayClient extends AbstractPayClient<AlipayPayCl
|
|||
//支付宝不返回退款单号,设置为空
|
||||
PayRefundUnifiedRespDTO respDTO = new PayRefundUnifiedRespDTO();
|
||||
respDTO.setChannelRefundId("");
|
||||
return PayCommonResult.build(response.getCode(), response.getMsg(), respDTO, codeMapping);
|
||||
// return PayCommonResult.build(response.getCode(), response.getMsg(), respDTO, codeMapping); TODO
|
||||
return null;
|
||||
}
|
||||
// 失败。需要抛出异常
|
||||
return PayCommonResult.build(response.getCode(), response.getMsg(), null, codeMapping);
|
||||
// return PayCommonResult.build(response.getCode(), response.getMsg(), null, codeMapping); TODO
|
||||
return null;
|
||||
} catch (AlipayApiException e) {
|
||||
// TODO 记录异常日志
|
||||
log.error("[doUnifiedRefund][request({}) 发起退款失败,网络读超时,退款状态未知]", toJsonString(reqDTO), e);
|
||||
return PayCommonResult.build(e.getErrCode(), e.getErrMsg(), null, codeMapping);
|
||||
// return PayCommonResult.build(e.getErrCode(), e.getErrMsg(), null, codeMapping); TODO
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public Object parseNotify(PayNotifyReqDTO rawNotify) {
|
||||
// 1. 校验回调数据
|
||||
String body = rawNotify.getBody();
|
||||
Map<String, String> params = rawNotify.getParams();
|
||||
Map<String, String> bodyObj = HttpUtil.decodeParamMap(body, StandardCharsets.UTF_8);
|
||||
AlipaySignature.rsaCheckV1(bodyObj, config.getAlipayPublicKey(),
|
||||
StandardCharsets.UTF_8.name(), "RSA2");
|
||||
|
||||
|
||||
/**
|
||||
* 支付宝统一回调参数 str 转 map
|
||||
*
|
||||
* @param s 支付宝支付通知回调参数
|
||||
* @return map 支付宝集合
|
||||
*/
|
||||
public static Map<String, String> strToMap(String s) {
|
||||
// TODO @zxy:这个可以使用 hutool 的 HttpUtil decodeParams 方法么?
|
||||
Map<String, String> stringStringMap = new HashMap<>();
|
||||
// 调整时间格式
|
||||
String s3 = s.replaceAll("%3A", ":");
|
||||
// 获取 map
|
||||
String s4 = s3.replace("+", " ");
|
||||
String[] split = s4.split("&");
|
||||
for (String s1 : split) {
|
||||
String[] split1 = s1.split("=");
|
||||
stringStringMap.put(split1[0], split1[1]);
|
||||
// 2.1 退款的情况
|
||||
if (bodyObj.containsKey("refund_fee")) {
|
||||
return PayRefundNotifyRespDTO.builder().channelOrderNo(bodyObj.get("trade_no"))
|
||||
.tradeNo(bodyObj.get("out_trade_no")).reqNo(bodyObj.get("out_biz_no"))
|
||||
.status(PayNotifyRefundStatusEnum.SUCCESS)
|
||||
.refundSuccessTime(parseTime(params.get("gmt_refund")))
|
||||
.build();
|
||||
}
|
||||
return stringStringMap;
|
||||
// 2.2 支付的情况
|
||||
return PayOrderNotifyRespDTO.builder().orderExtensionNo(bodyObj.get("out_trade_no"))
|
||||
.channelOrderNo(bodyObj.get("trade_no")).channelUserId(bodyObj.get("seller_id"))
|
||||
.tradeStatus(bodyObj.get("trade_status")).successTime(parseTime(params.get("notify_time")))
|
||||
.build();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,57 @@
|
|||
package cn.iocoder.yudao.framework.pay.core.client.impl.alipay;
|
||||
|
||||
import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderUnifiedReqDTO;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderUnifiedRespDTO;
|
||||
import cn.iocoder.yudao.framework.pay.core.enums.PayChannelEnum;
|
||||
import cn.iocoder.yudao.framework.pay.core.enums.PayDisplayModeEnum;
|
||||
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 实现类
|
||||
*
|
||||
* 文档:<a href="https://opendocs.alipay.com/open/02e7gq">App 支付</a>
|
||||
*
|
||||
* // TODO 芋艿:未详细测试,因为手头没 App
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Slf4j
|
||||
public class AlipayAppPayClient extends AbstractAlipayClient {
|
||||
|
||||
public AlipayAppPayClient(Long channelId, AlipayPayClientConfig config) {
|
||||
super(channelId, PayChannelEnum.ALIPAY_APP.getCode(), config);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PayOrderUnifiedRespDTO doUnifiedOrder(PayOrderUnifiedReqDTO reqDTO) throws AlipayApiException {
|
||||
// 1.1 构建 AlipayTradeAppPayModel 请求
|
||||
AlipayTradeAppPayModel model = new AlipayTradeAppPayModel();
|
||||
// ① 通用的参数
|
||||
model.setOutTradeNo(reqDTO.getMerchantOrderId());
|
||||
model.setSubject(reqDTO.getSubject());
|
||||
model.setBody(reqDTO.getBody());
|
||||
model.setTotalAmount(formatAmount(reqDTO.getAmount()));
|
||||
model.setProductCode(" QUICK_MSECURITY_PAY"); // 销售产品码:无线快捷支付产品
|
||||
// ② 个性化的参数【无】
|
||||
// ③ 支付宝扫码支付只有一种展示
|
||||
String displayMode = PayDisplayModeEnum.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.execute(request);
|
||||
// 2.2 处理结果
|
||||
validateSuccess(response);
|
||||
return new PayOrderUnifiedRespDTO()
|
||||
.setDisplayMode(displayMode).setDisplayContent("");
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,66 @@
|
|||
package cn.iocoder.yudao.framework.pay.core.client.impl.alipay;
|
||||
|
||||
import cn.hutool.core.map.MapUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderUnifiedReqDTO;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderUnifiedRespDTO;
|
||||
import cn.iocoder.yudao.framework.pay.core.enums.PayChannelEnum;
|
||||
import cn.iocoder.yudao.framework.pay.core.enums.PayDisplayModeEnum;
|
||||
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 static cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants.BAD_REQUEST;
|
||||
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
|
||||
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception0;
|
||||
|
||||
/**
|
||||
* 支付宝【条码支付】的 PayClient 实现类
|
||||
*
|
||||
* 文档:<a href="https://opendocs.alipay.com/open/194/105072">当面付</a>
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Slf4j
|
||||
public class AlipayBarPayClient extends AbstractAlipayClient {
|
||||
|
||||
public AlipayBarPayClient(Long channelId, AlipayPayClientConfig config) {
|
||||
super(channelId, PayChannelEnum.ALIPAY_BAR.getCode(), config);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PayOrderUnifiedRespDTO 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.getMerchantOrderId());
|
||||
model.setSubject(reqDTO.getSubject());
|
||||
model.setBody(reqDTO.getBody());
|
||||
model.setTotalAmount(formatAmount(reqDTO.getAmount()));
|
||||
model.setScene("bar_code"); // 当面付条码支付场景
|
||||
// ② 个性化的参数
|
||||
model.setAuthCode(authCode);
|
||||
// ③ 支付宝条码支付只有一种展示
|
||||
String displayMode = PayDisplayModeEnum.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 = client.execute(request);
|
||||
// 2.2 处理结果
|
||||
validateSuccess(response);
|
||||
return new PayOrderUnifiedRespDTO()
|
||||
.setDisplayMode(displayMode).setDisplayContent("");
|
||||
}
|
||||
}
|
|
@ -1,28 +0,0 @@
|
|||
package cn.iocoder.yudao.framework.pay.core.client.impl.alipay;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.exception.ErrorCode;
|
||||
import cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.AbstractPayCodeMapping;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* 支付宝的 PayCodeMapping 实现类
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
public class AlipayPayCodeMapping extends AbstractPayCodeMapping {
|
||||
|
||||
@Override
|
||||
protected ErrorCode apply0(String apiCode, String apiMsg) {
|
||||
if (Objects.equals(apiCode, "10000")) {
|
||||
return GlobalErrorCodeConstants.SUCCESS;
|
||||
}
|
||||
// alipay wap api code 返回为null, 暂时定为-9999
|
||||
if (Objects.equals(apiCode, "-9999")) {
|
||||
return GlobalErrorCodeConstants.SUCCESS;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
|
@ -1,21 +1,24 @@
|
|||
package cn.iocoder.yudao.framework.pay.core.client.impl.alipay;
|
||||
|
||||
import cn.hutool.core.map.MapUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.PayCommonResult;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.dto.PayOrderUnifiedReqDTO;
|
||||
import cn.hutool.http.Method;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderUnifiedReqDTO;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderUnifiedRespDTO;
|
||||
import cn.iocoder.yudao.framework.pay.core.enums.PayChannelEnum;
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import cn.iocoder.yudao.framework.pay.core.enums.PayDisplayModeEnum;
|
||||
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 实现类
|
||||
* 文档:https://opendocs.alipay.com/open/270/105898
|
||||
* 支付宝【PC 网站】的 PayClient 实现类
|
||||
*
|
||||
* 文档:<a href="https://opendocs.alipay.com/open/270/105898">电脑网站支付</a>
|
||||
*
|
||||
* @author XGD
|
||||
*/
|
||||
|
@ -23,38 +26,66 @@ import lombok.extern.slf4j.Slf4j;
|
|||
public class AlipayPcPayClient extends AbstractAlipayClient {
|
||||
|
||||
public AlipayPcPayClient(Long channelId, AlipayPayClientConfig config) {
|
||||
super(channelId, PayChannelEnum.ALIPAY_PC.getCode(), config, new AlipayPayCodeMapping());
|
||||
super(channelId, PayChannelEnum.ALIPAY_PC.getCode(), config);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PayCommonResult<AlipayTradePagePayResponse> doUnifiedOrder(PayOrderUnifiedReqDTO reqDTO) {
|
||||
// 构建 AlipayTradePagePayModel 请求
|
||||
public PayOrderUnifiedRespDTO doUnifiedOrder(PayOrderUnifiedReqDTO reqDTO) throws AlipayApiException {
|
||||
// 1.1 构建 AlipayTradePagePayModel 请求
|
||||
AlipayTradePagePayModel model = new AlipayTradePagePayModel();
|
||||
// 构建 AlipayTradePagePayRequest
|
||||
// ① 通用的参数
|
||||
model.setOutTradeNo(reqDTO.getMerchantOrderId());
|
||||
model.setSubject(reqDTO.getSubject());
|
||||
model.setBody(reqDTO.getBody());
|
||||
model.setTotalAmount(formatAmount(reqDTO.getAmount()));
|
||||
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(MapUtil.getStr(reqDTO.getChannelExtras(), "qr_pay_mode"));
|
||||
model.setQrcodeWidth(MapUtil.getLong(reqDTO.getChannelExtras(), "qr_code_width"));
|
||||
// ③ 支付宝 PC 支付有多种展示模式,因此这里需要计算
|
||||
String displayMode = getDisplayMode(reqDTO.getDisplayMode(), model.getQrPayMode());
|
||||
|
||||
// 1.2 构建 AlipayTradePagePayRequest 请求
|
||||
AlipayTradePagePayRequest request = new AlipayTradePagePayRequest();
|
||||
request.setBizModel(model);
|
||||
JSONObject bizContent = new JSONObject();
|
||||
// 参数说明可查看: https://opendocs.alipay.com/open/028r8t?scene=22
|
||||
bizContent.put("out_trade_no", reqDTO.getMerchantOrderId());
|
||||
bizContent.put("total_amount", calculateAmount(reqDTO.getAmount()));
|
||||
bizContent.put("subject", reqDTO.getSubject());
|
||||
bizContent.put("product_code", "FAST_INSTANT_TRADE_PAY");
|
||||
// PC扫码支付的方式:支持前置模式和跳转模式。4: 订单码-可定义宽度的嵌入式二维码
|
||||
bizContent.put("qr_pay_mode", "4");
|
||||
// 自定义二维码宽度
|
||||
bizContent.put("qrcode_width", "150");
|
||||
request.setBizContent(bizContent.toJSONString());
|
||||
request.setNotifyUrl(reqDTO.getNotifyUrl());
|
||||
request.setReturnUrl("");
|
||||
// 执行请求
|
||||
request.setReturnUrl(reqDTO.getReturnUrl());
|
||||
|
||||
// 2.1 执行请求
|
||||
AlipayTradePagePayResponse response;
|
||||
try {
|
||||
response = client.pageExecute(request);
|
||||
} catch (AlipayApiException e) {
|
||||
log.error("[unifiedOrder][request({}) 发起支付失败]", JsonUtils.toJsonString(reqDTO), e);
|
||||
return PayCommonResult.build(e.getErrCode(), e.getErrMsg(), null, codeMapping);
|
||||
if (Objects.equals(displayMode, PayDisplayModeEnum.FORM.getMode())) {
|
||||
response = client.pageExecute(request, Method.POST.name()); // 需要特殊使用 POST 请求
|
||||
} else {
|
||||
response = client.pageExecute(request, Method.GET.name());
|
||||
}
|
||||
// 响应为表单格式,前端可嵌入响应的页面或关闭当前支付窗口
|
||||
return PayCommonResult.build(StrUtil.blankToDefault(response.getCode(),"10000") ,response.getMsg(), response, codeMapping);
|
||||
|
||||
// 2.2 处理结果
|
||||
validateSuccess(response);
|
||||
return new PayOrderUnifiedRespDTO().setDisplayMode(displayMode)
|
||||
.setDisplayContent(response.getBody());
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得最终的支付 UI 展示模式
|
||||
*
|
||||
* @param displayMode 前端传递的 UI 展示模式
|
||||
* @param qrPayMode 前端传递的二维码模式
|
||||
* @return 最终的支付 UI 展示模式
|
||||
*/
|
||||
private String getDisplayMode(String displayMode, String qrPayMode) {
|
||||
// 1.1 支付宝二维码的前置模式
|
||||
if (StrUtil.equalsAny(qrPayMode, "0", "1", "3", "4")) {
|
||||
return PayDisplayModeEnum.IFRAME.getMode();
|
||||
}
|
||||
// 1.2 支付宝二维码的跳转模式
|
||||
if (StrUtil.equals(qrPayMode, "2")) {
|
||||
return PayDisplayModeEnum.URL.getMode();
|
||||
}
|
||||
// 2. 前端传递了 UI 展示模式
|
||||
return displayMode != null ? displayMode :
|
||||
PayDisplayModeEnum.URL.getMode(); // 模式使用 URL 跳转
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,19 +1,19 @@
|
|||
package cn.iocoder.yudao.framework.pay.core.client.impl.alipay;
|
||||
|
||||
import cn.iocoder.yudao.framework.pay.core.client.PayCommonResult;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.dto.PayOrderUnifiedReqDTO;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderUnifiedReqDTO;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderUnifiedRespDTO;
|
||||
import cn.iocoder.yudao.framework.pay.core.enums.PayChannelEnum;
|
||||
import cn.iocoder.yudao.framework.pay.core.enums.PayDisplayModeEnum;
|
||||
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 static cn.iocoder.yudao.framework.common.util.json.JsonUtils.toJsonString;
|
||||
|
||||
/**
|
||||
* 支付宝【扫码支付】的 PayClient 实现类
|
||||
* 文档:https://opendocs.alipay.com/apis/02890k
|
||||
*
|
||||
* 文档:<a href="https://opendocs.alipay.com/apis/02890k">扫码支付</a>
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
|
@ -21,32 +21,34 @@ import static cn.iocoder.yudao.framework.common.util.json.JsonUtils.toJsonString
|
|||
public class AlipayQrPayClient extends AbstractAlipayClient {
|
||||
|
||||
public AlipayQrPayClient(Long channelId, AlipayPayClientConfig config) {
|
||||
super(channelId, PayChannelEnum.ALIPAY_QR.getCode(), config, new AlipayPayCodeMapping());
|
||||
super(channelId, PayChannelEnum.ALIPAY_QR.getCode(), config);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PayCommonResult<AlipayTradePrecreateResponse> doUnifiedOrder(PayOrderUnifiedReqDTO reqDTO) {
|
||||
// 构建 AlipayTradePrecreateModel 请求
|
||||
public PayOrderUnifiedRespDTO doUnifiedOrder(PayOrderUnifiedReqDTO reqDTO) throws AlipayApiException {
|
||||
// 1.1 构建 AlipayTradePrecreateModel 请求
|
||||
AlipayTradePrecreateModel model = new AlipayTradePrecreateModel();
|
||||
// ① 通用的参数
|
||||
model.setOutTradeNo(reqDTO.getMerchantOrderId());
|
||||
model.setSubject(reqDTO.getSubject());
|
||||
model.setBody(reqDTO.getBody());
|
||||
model.setTotalAmount(calculateAmount(reqDTO.getAmount()).toString()); // 单位:元
|
||||
// TODO 芋艿:userIp + expireTime
|
||||
// 构建 AlipayTradePrecreateRequest
|
||||
model.setTotalAmount(formatAmount(reqDTO.getAmount()));
|
||||
model.setProductCode("FACE_TO_FACE_PAYMENT"); // 销售产品码. 目前扫码支付场景下仅支持 FACE_TO_FACE_PAYMENT
|
||||
// ② 个性化的参数【无】
|
||||
// ③ 支付宝扫码支付只有一种展示,考虑到前端可能希望二维码扫描后,手机打开
|
||||
String displayMode = PayDisplayModeEnum.QR_CODE.getMode();
|
||||
|
||||
// 1.2 构建 AlipayTradePrecreateRequest 请求
|
||||
AlipayTradePrecreateRequest request = new AlipayTradePrecreateRequest();
|
||||
request.setBizModel(model);
|
||||
request.setNotifyUrl(reqDTO.getNotifyUrl());
|
||||
request.setReturnUrl(reqDTO.getReturnUrl());
|
||||
// 执行请求
|
||||
AlipayTradePrecreateResponse response;
|
||||
try {
|
||||
response = client.execute(request);
|
||||
} catch (AlipayApiException e) {
|
||||
log.error("[unifiedOrder][request({}) 发起支付失败]", toJsonString(reqDTO), e);
|
||||
return PayCommonResult.build(e.getErrCode(), e.getErrMsg(), null, codeMapping);
|
||||
}
|
||||
// TODO 芋艿:sub Code 需要测试下各种失败的情况
|
||||
return PayCommonResult.build(response.getCode(), response.getMsg(), response, codeMapping);
|
||||
|
||||
// 2.1 执行请求
|
||||
AlipayTradePrecreateResponse response = client.execute(request);
|
||||
// 2.2 处理结果
|
||||
validateSuccess(response);
|
||||
return new PayOrderUnifiedRespDTO()
|
||||
.setDisplayMode(displayMode).setDisplayContent(response.getQrCode());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,75 +1,60 @@
|
|||
package cn.iocoder.yudao.framework.pay.core.client.impl.alipay;
|
||||
|
||||
import cn.hutool.core.date.DateUtil;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.PayCommonResult;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.dto.PayOrderUnifiedReqDTO;
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import cn.hutool.http.Method;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderUnifiedReqDTO;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderUnifiedRespDTO;
|
||||
import cn.iocoder.yudao.framework.pay.core.enums.PayChannelEnum;
|
||||
import cn.iocoder.yudao.framework.pay.core.enums.PayDisplayModeEnum;
|
||||
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;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* 支付宝【手机网站】的 PayClient 实现类
|
||||
* 文档:https://opendocs.alipay.com/apis/api_1/alipay.trade.wap.pay
|
||||
* 支付宝【Wap 网站】的 PayClient 实现类
|
||||
*
|
||||
* 文档:<a href="https://opendocs.alipay.com/apis/api_1/alipay.trade.wap.pay">手机网站支付接口</a>
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Slf4j
|
||||
public class AlipayWapPayClient extends AbstractAlipayClient {
|
||||
|
||||
|
||||
public AlipayWapPayClient(Long channelId, AlipayPayClientConfig config) {
|
||||
super(channelId, PayChannelEnum.ALIPAY_WAP.getCode(), config, new AlipayPayCodeMapping());
|
||||
super(channelId, PayChannelEnum.ALIPAY_WAP.getCode(), config);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PayCommonResult<AlipayTradeWapPayResponse> doUnifiedOrder(PayOrderUnifiedReqDTO reqDTO) {
|
||||
// 构建 AlipayTradeWapPayModel 请求
|
||||
public PayOrderUnifiedRespDTO doUnifiedOrder(PayOrderUnifiedReqDTO reqDTO) throws AlipayApiException {
|
||||
// 1.1 构建 AlipayTradeWapPayModel 请求
|
||||
AlipayTradeWapPayModel model = new AlipayTradeWapPayModel();
|
||||
// ① 通用的参数
|
||||
model.setOutTradeNo(reqDTO.getMerchantOrderId());
|
||||
model.setSubject(reqDTO.getSubject());
|
||||
model.setBody(reqDTO.getBody());
|
||||
model.setTotalAmount(calculateAmount(reqDTO.getAmount()).toString());
|
||||
model.setProductCode("QUICK_WAP_PAY"); // TODO 芋艿:这里咋整
|
||||
//TODO 芋艿:这里咋整 jason @芋艿 可以去掉吧,
|
||||
// TODO 芋艿 似乎这里不用传sellerId
|
||||
// https://opendocs.alipay.com/apis/api_1/alipay.trade.wap.pay
|
||||
//model.setSellerId("2088102147948060");
|
||||
model.setTimeExpire(DateUtil.format(reqDTO.getExpireTime(),"yyyy-MM-dd HH:mm:ss"));
|
||||
// TODO 芋艿:userIp
|
||||
// 构建 AlipayTradeWapPayRequest
|
||||
model.setTotalAmount(formatAmount(reqDTO.getAmount()));
|
||||
model.setProductCode("QUICK_WAP_PAY"); // 销售产品码. 目前 Wap 支付场景下仅支持 QUICK_WAP_PAY
|
||||
// ② 个性化的参数【无】
|
||||
// ③ 支付宝 Wap 支付只有一种展示,考虑到前端可能希望二维码扫描后,手机打开
|
||||
String displayMode = ObjectUtil.defaultIfNull(reqDTO.getDisplayMode(),
|
||||
PayDisplayModeEnum.URL.getMode());
|
||||
|
||||
// 1.2 构建 AlipayTradeWapPayRequest 请求
|
||||
AlipayTradeWapPayRequest request = new AlipayTradeWapPayRequest();
|
||||
request.setBizModel(model);
|
||||
request.setNotifyUrl(reqDTO.getNotifyUrl());
|
||||
request.setReturnUrl(reqDTO.getReturnUrl());
|
||||
model.setQuitUrl(reqDTO.getReturnUrl());
|
||||
|
||||
// 执行请求
|
||||
AlipayTradeWapPayResponse response;
|
||||
try {
|
||||
response = client.pageExecute(request);
|
||||
} catch (AlipayApiException e) {
|
||||
return PayCommonResult.build(e.getErrCode(), e.getErrMsg(), null, codeMapping);
|
||||
// 2.1 执行请求
|
||||
AlipayTradeWapPayResponse response = client.pageExecute(request, Method.GET.name());
|
||||
|
||||
// 2.2 处理结果
|
||||
validateSuccess(response);
|
||||
return new PayOrderUnifiedRespDTO()
|
||||
.setDisplayMode(displayMode).setDisplayContent(response.getBody());
|
||||
}
|
||||
|
||||
// TODO 芋艿:sub Code
|
||||
if(response.isSuccess() && Objects.isNull(response.getCode()) && Objects.nonNull(response.getBody())){
|
||||
//成功alipay wap 成功 code 为 null , body 为form 表单
|
||||
return PayCommonResult.build("-9999", "Success", response, codeMapping);
|
||||
}else {
|
||||
return PayCommonResult.build(response.getCode(), response.getMsg(), response, codeMapping);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -1,56 +0,0 @@
|
|||
package cn.iocoder.yudao.framework.pay.core.client.impl.wx;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.iocoder.yudao.framework.common.exception.ErrorCode;
|
||||
import cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.AbstractPayCodeMapping;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
import static cn.iocoder.yudao.framework.pay.core.enums.PayFrameworkErrorCodeConstants.*;
|
||||
|
||||
/**
|
||||
* 微信支付 PayCodeMapping 实现类
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
public class WXCodeMapping extends AbstractPayCodeMapping {
|
||||
|
||||
/**
|
||||
* 错误码 - 成功
|
||||
* 由于 weixin-java-pay 封装的 Result 未返回 code,所以自己定义下
|
||||
*/
|
||||
public static final String CODE_SUCCESS = "SUCCESS";
|
||||
/**
|
||||
* 错误提示 - 成功
|
||||
*/
|
||||
public static final String MESSAGE_SUCCESS = "成功";
|
||||
|
||||
@Override
|
||||
protected ErrorCode apply0(String apiCode, String apiMsg) {
|
||||
if (Objects.equals(apiCode, CODE_SUCCESS)) {
|
||||
return GlobalErrorCodeConstants.SUCCESS;
|
||||
}
|
||||
if (Objects.equals(apiCode, "FAIL")) {
|
||||
if (Objects.equals(apiMsg, "AppID不存在,请检查后再试")) {
|
||||
return PAY_CONFIG_APP_ID_ERROR;
|
||||
}
|
||||
if (Objects.equals(apiMsg, "签名错误,请检查后再试")
|
||||
|| Objects.equals(apiMsg, "签名错误")) {
|
||||
return PAY_CONFIG_SIGN_ERROR;
|
||||
}
|
||||
}
|
||||
if (Objects.equals(apiCode, "PARAM_ERROR")) {
|
||||
if (Objects.equals(apiMsg, "无效的openid")) {
|
||||
return PAY_OPENID_ERROR;
|
||||
}
|
||||
}
|
||||
if (Objects.equals(apiCode, "CustomErrorCode")) {
|
||||
if (StrUtil.contains(apiMsg, "必填字段")) {
|
||||
return PAY_PARAM_MISSING;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
|
@ -7,9 +7,13 @@ import cn.hutool.core.lang.Assert;
|
|||
import cn.hutool.core.map.MapUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.iocoder.yudao.framework.common.util.io.FileUtils;
|
||||
import cn.iocoder.yudao.framework.common.util.object.ObjectUtils;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.PayCommonResult;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.dto.*;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.dto.notify.PayNotifyReqDTO;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.dto.notify.PayOrderNotifyRespDTO;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.dto.notify.PayRefundNotifyRespDTO;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderUnifiedReqDTO;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderUnifiedRespDTO;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.dto.refund.PayRefundUnifiedReqDTO;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.dto.refund.PayRefundUnifiedRespDTO;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.impl.AbstractPayClient;
|
||||
import cn.iocoder.yudao.framework.pay.core.enums.PayChannelEnum;
|
||||
import com.github.binarywang.wxpay.bean.notify.WxPayOrderNotifyResult;
|
||||
|
@ -30,11 +34,6 @@ import java.time.ZoneId;
|
|||
import java.util.Date;
|
||||
import java.util.Objects;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.util.json.JsonUtils.toJsonString;
|
||||
import static cn.iocoder.yudao.framework.pay.core.client.impl.wx.WXCodeMapping.CODE_SUCCESS;
|
||||
import static cn.iocoder.yudao.framework.pay.core.client.impl.wx.WXCodeMapping.MESSAGE_SUCCESS;
|
||||
|
||||
|
||||
/**
|
||||
* 微信小程序下支付
|
||||
*
|
||||
|
@ -46,7 +45,7 @@ public class WXLitePayClient extends AbstractPayClient<WXPayClientConfig> {
|
|||
private WxPayService client;
|
||||
|
||||
public WXLitePayClient(Long channelId, WXPayClientConfig config) {
|
||||
super(channelId, PayChannelEnum.WX_LITE.getCode(), config, new WXCodeMapping());
|
||||
super(channelId, PayChannelEnum.WX_LITE.getCode(), config);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -71,28 +70,29 @@ public class WXLitePayClient extends AbstractPayClient<WXPayClientConfig> {
|
|||
}
|
||||
|
||||
@Override
|
||||
public PayCommonResult<WxPayMpOrderResult> doUnifiedOrder(PayOrderUnifiedReqDTO reqDTO) {
|
||||
WxPayMpOrderResult response;
|
||||
try {
|
||||
switch (config.getApiVersion()) {
|
||||
case WXPayClientConfig.API_VERSION_V2:
|
||||
response = this.unifiedOrderV2(reqDTO);
|
||||
break;
|
||||
case WXPayClientConfig.API_VERSION_V3:
|
||||
WxPayUnifiedOrderV3Result.JsapiResult responseV3 = this.unifiedOrderV3(reqDTO);
|
||||
// 将 V3 的结果,统一转换成 V2。返回的字段是一致的
|
||||
response = new WxPayMpOrderResult();
|
||||
BeanUtil.copyProperties(responseV3, response, true);
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException(String.format("未知的 API 版本(%s)", config.getApiVersion()));
|
||||
}
|
||||
} catch (WxPayException e) {
|
||||
log.error("[unifiedOrder][request({}) 发起支付失败,原因({})]", toJsonString(reqDTO), e);
|
||||
return PayCommonResult.build(ObjectUtils.defaultIfNull(e.getErrCode(), e.getReturnCode(), "CustomErrorCode"),
|
||||
ObjectUtils.defaultIfNull(e.getErrCodeDes(), e.getCustomErrorMsg()), null, codeMapping);
|
||||
}
|
||||
return PayCommonResult.build(CODE_SUCCESS, MESSAGE_SUCCESS, response, codeMapping);
|
||||
public PayOrderUnifiedRespDTO doUnifiedOrder(PayOrderUnifiedReqDTO reqDTO) {
|
||||
throw new UnsupportedOperationException();
|
||||
// WxPayMpOrderResult response;
|
||||
// try {
|
||||
// switch (config.getApiVersion()) {
|
||||
// case WXPayClientConfig.API_VERSION_V2:
|
||||
// response = this.unifiedOrderV2(reqDTO);
|
||||
// break;
|
||||
// case WXPayClientConfig.API_VERSION_V3:
|
||||
// WxPayUnifiedOrderV3Result.JsapiResult responseV3 = this.unifiedOrderV3(reqDTO);
|
||||
// // 将 V3 的结果,统一转换成 V2。返回的字段是一致的
|
||||
// response = new WxPayMpOrderResult();
|
||||
// BeanUtil.copyProperties(responseV3, response, true);
|
||||
// break;
|
||||
// default:
|
||||
// throw new IllegalArgumentException(String.format("未知的 API 版本(%s)", config.getApiVersion()));
|
||||
// }
|
||||
// } catch (WxPayException e) {
|
||||
// log.error("[unifiedOrder][request({}) 发起支付失败,原因({})]", toJsonString(reqDTO), e);
|
||||
// return PayCommonResult.build(ObjectUtils.defaultIfNull(e.getErrCode(), e.getReturnCode(), "CustomErrorCode"),
|
||||
// ObjectUtils.defaultIfNull(e.getErrCodeDes(), e.getCustomErrorMsg()), null, codeMapping);
|
||||
// }
|
||||
// return PayCommonResult.build(CODE_SUCCESS, MESSAGE_SUCCESS, response, codeMapping);
|
||||
}
|
||||
|
||||
private WxPayMpOrderResult unifiedOrderV2(PayOrderUnifiedReqDTO reqDTO) throws WxPayException {
|
||||
|
@ -145,8 +145,8 @@ public class WXLitePayClient extends AbstractPayClient<WXPayClientConfig> {
|
|||
* @return 支付回调对象
|
||||
* @throws WxPayException 微信异常类
|
||||
*/
|
||||
@Override
|
||||
public PayOrderNotifyRespDTO parseOrderNotify(PayNotifyDataDTO data) throws WxPayException {
|
||||
// @Override
|
||||
public PayOrderNotifyRespDTO parseOrderNotify(PayNotifyReqDTO data) throws WxPayException {
|
||||
log.info("[parseOrderNotify][微信支付回调data数据:{}]", data.getBody());
|
||||
// 微信支付 v2 回调结果处理
|
||||
switch (config.getApiVersion()) {
|
||||
|
@ -159,7 +159,7 @@ public class WXLitePayClient extends AbstractPayClient<WXPayClientConfig> {
|
|||
}
|
||||
}
|
||||
|
||||
private PayOrderNotifyRespDTO parseOrderNotifyV3(PayNotifyDataDTO data) throws WxPayException {
|
||||
private PayOrderNotifyRespDTO parseOrderNotifyV3(PayNotifyReqDTO data) throws WxPayException {
|
||||
WxPayOrderNotifyV3Result wxPayOrderNotifyV3Result = client.parseOrderNotifyV3Result(data.getBody(), null);
|
||||
WxPayOrderNotifyV3Result.DecryptNotifyResult result = wxPayOrderNotifyV3Result.getResult();
|
||||
// 转换结果
|
||||
|
@ -172,11 +172,10 @@ public class WXLitePayClient extends AbstractPayClient<WXPayClientConfig> {
|
|||
.channelOrderNo(result.getTransactionId())
|
||||
.channelUserId(result.getPayer().getOpenid())
|
||||
.successTime(LocalDateTimeUtil.parse(result.getSuccessTime(), "yyyy-MM-dd'T'HH:mm:ssXXX"))
|
||||
.data(data.getBody())
|
||||
.build();
|
||||
}
|
||||
|
||||
private PayOrderNotifyRespDTO parseOrderNotifyV2(PayNotifyDataDTO data) throws WxPayException {
|
||||
private PayOrderNotifyRespDTO parseOrderNotifyV2(PayNotifyReqDTO data) throws WxPayException {
|
||||
WxPayOrderNotifyResult notifyResult = client.parseOrderNotifyResult(data.getBody());
|
||||
Assert.isTrue(Objects.equals(notifyResult.getResultCode(), "SUCCESS"), "支付结果非 SUCCESS");
|
||||
// 转换结果
|
||||
|
@ -186,20 +185,12 @@ public class WXLitePayClient extends AbstractPayClient<WXPayClientConfig> {
|
|||
.channelOrderNo(notifyResult.getTransactionId())
|
||||
.channelUserId(notifyResult.getOpenid())
|
||||
.successTime(LocalDateTimeUtil.parse(notifyResult.getTimeEnd(), "yyyyMMddHHmmss"))
|
||||
.data(data.getBody())
|
||||
.build();
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public PayRefundNotifyDTO parseRefundNotify(PayNotifyDataDTO notifyData) {
|
||||
//TODO 需要实现
|
||||
throw new UnsupportedOperationException("需要实现");
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected PayCommonResult<PayRefundUnifiedRespDTO> doUnifiedRefund(PayRefundUnifiedReqDTO reqDTO) throws Throwable {
|
||||
protected PayRefundUnifiedRespDTO doUnifiedRefund(PayRefundUnifiedReqDTO reqDTO) {
|
||||
//TODO 需要实现
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
|
|
@ -6,9 +6,13 @@ import cn.hutool.core.date.LocalDateTimeUtil;
|
|||
import cn.hutool.core.lang.Assert;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.iocoder.yudao.framework.common.util.io.FileUtils;
|
||||
import cn.iocoder.yudao.framework.common.util.object.ObjectUtils;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.PayCommonResult;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.dto.*;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.dto.notify.PayNotifyReqDTO;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.dto.notify.PayOrderNotifyRespDTO;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.dto.notify.PayRefundNotifyRespDTO;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderUnifiedReqDTO;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderUnifiedRespDTO;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.dto.refund.PayRefundUnifiedReqDTO;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.dto.refund.PayRefundUnifiedRespDTO;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.impl.AbstractPayClient;
|
||||
import cn.iocoder.yudao.framework.pay.core.enums.PayChannelEnum;
|
||||
import com.github.binarywang.wxpay.bean.notify.WxPayOrderNotifyResult;
|
||||
|
@ -28,10 +32,6 @@ import java.time.ZoneId;
|
|||
import java.util.Date;
|
||||
import java.util.Objects;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.util.json.JsonUtils.toJsonString;
|
||||
import static cn.iocoder.yudao.framework.pay.core.client.impl.wx.WXCodeMapping.CODE_SUCCESS;
|
||||
import static cn.iocoder.yudao.framework.pay.core.client.impl.wx.WXCodeMapping.MESSAGE_SUCCESS;
|
||||
|
||||
/**
|
||||
* 微信 App 支付
|
||||
*
|
||||
|
@ -43,7 +43,7 @@ public class WXNativePayClient extends AbstractPayClient<WXPayClientConfig> {
|
|||
private WxPayService client;
|
||||
|
||||
public WXNativePayClient(Long channelId, WXPayClientConfig config) {
|
||||
super(channelId, PayChannelEnum.WX_NATIVE.getCode(), config, new WXCodeMapping());
|
||||
super(channelId, PayChannelEnum.WX_NATIVE.getCode(), config);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -68,27 +68,28 @@ public class WXNativePayClient extends AbstractPayClient<WXPayClientConfig> {
|
|||
}
|
||||
|
||||
@Override
|
||||
public PayCommonResult<String> doUnifiedOrder(PayOrderUnifiedReqDTO reqDTO) {
|
||||
// 这里原生的返回的是支付的 url 所以直接使用string接收
|
||||
// "invokeResponse": "weixin://wxpay/bizpayurl?pr=EGYAem7zz"
|
||||
String responseV3;
|
||||
try {
|
||||
switch (config.getApiVersion()) {
|
||||
case WXPayClientConfig.API_VERSION_V2:
|
||||
responseV3 = unifiedOrderV2(reqDTO).getCodeUrl();
|
||||
break;
|
||||
case WXPayClientConfig.API_VERSION_V3:
|
||||
responseV3 = this.unifiedOrderV3(reqDTO);
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException(String.format("未知的 API 版本(%s)", config.getApiVersion()));
|
||||
}
|
||||
} catch (WxPayException e) {
|
||||
log.error("[unifiedOrder][request({}) 发起支付失败,原因({})]", toJsonString(reqDTO), e);
|
||||
return PayCommonResult.build(ObjectUtils.defaultIfNull(e.getErrCode(), e.getReturnCode(), "CustomErrorCode"),
|
||||
ObjectUtils.defaultIfNull(e.getErrCodeDes(), e.getCustomErrorMsg()), null, codeMapping);
|
||||
}
|
||||
return PayCommonResult.build(CODE_SUCCESS, MESSAGE_SUCCESS, responseV3, codeMapping);
|
||||
public PayOrderUnifiedRespDTO doUnifiedOrder(PayOrderUnifiedReqDTO reqDTO) {
|
||||
throw new UnsupportedOperationException();
|
||||
// // 这里原生的返回的是支付的 url 所以直接使用string接收
|
||||
// // "invokeResponse": "weixin://wxpay/bizpayurl?pr=EGYAem7zz"
|
||||
// String responseV3;
|
||||
// try {
|
||||
// switch (config.getApiVersion()) {
|
||||
// case WXPayClientConfig.API_VERSION_V2:
|
||||
// responseV3 = unifiedOrderV2(reqDTO).getCodeUrl();
|
||||
// break;
|
||||
// case WXPayClientConfig.API_VERSION_V3:
|
||||
// responseV3 = this.unifiedOrderV3(reqDTO);
|
||||
// break;
|
||||
// default:
|
||||
// throw new IllegalArgumentException(String.format("未知的 API 版本(%s)", config.getApiVersion()));
|
||||
// }
|
||||
// } catch (WxPayException e) {
|
||||
// log.error("[unifiedOrder][request({}) 发起支付失败,原因({})]", toJsonString(reqDTO), e);
|
||||
// return PayCommonResult.build(ObjectUtils.defaultIfNull(e.getErrCode(), e.getReturnCode(), "CustomErrorCode"),
|
||||
// ObjectUtils.defaultIfNull(e.getErrCodeDes(), e.getCustomErrorMsg()), null, codeMapping);
|
||||
// }
|
||||
// return PayCommonResult.build(CODE_SUCCESS, MESSAGE_SUCCESS, responseV3, codeMapping);
|
||||
}
|
||||
|
||||
private WxPayNativeOrderResult unifiedOrderV2(PayOrderUnifiedReqDTO reqDTO) throws WxPayException {
|
||||
|
@ -129,8 +130,8 @@ public class WXNativePayClient extends AbstractPayClient<WXPayClientConfig> {
|
|||
* @return 支付回调对象
|
||||
* @throws WxPayException 微信异常类
|
||||
*/
|
||||
@Override
|
||||
public PayOrderNotifyRespDTO parseOrderNotify(PayNotifyDataDTO data) throws WxPayException {
|
||||
// @Override
|
||||
public PayOrderNotifyRespDTO parseOrderNotify(PayNotifyReqDTO data) throws WxPayException {
|
||||
log.info("微信支付回调data数据:{}", data.getBody());
|
||||
// 微信支付 v2 回调结果处理
|
||||
switch (config.getApiVersion()) {
|
||||
|
@ -143,7 +144,7 @@ public class WXNativePayClient extends AbstractPayClient<WXPayClientConfig> {
|
|||
}
|
||||
}
|
||||
|
||||
private PayOrderNotifyRespDTO parseOrderNotifyV3(PayNotifyDataDTO data) throws WxPayException {
|
||||
private PayOrderNotifyRespDTO parseOrderNotifyV3(PayNotifyReqDTO data) throws WxPayException {
|
||||
WxPayOrderNotifyV3Result wxPayOrderNotifyV3Result = client.parseOrderNotifyV3Result(data.getBody(), null);
|
||||
WxPayOrderNotifyV3Result.DecryptNotifyResult result = wxPayOrderNotifyV3Result.getResult();
|
||||
// 转换结果
|
||||
|
@ -154,11 +155,10 @@ public class WXNativePayClient extends AbstractPayClient<WXPayClientConfig> {
|
|||
.orderExtensionNo(result.getOutTradeNo())
|
||||
.channelOrderNo(result.getTradeState())
|
||||
.successTime(LocalDateTimeUtil.parse(result.getSuccessTime(), "yyyy-MM-dd'T'HH:mm:ssXXX"))
|
||||
.data(data.getBody())
|
||||
.build();
|
||||
}
|
||||
|
||||
private PayOrderNotifyRespDTO parseOrderNotifyV2(PayNotifyDataDTO data) throws WxPayException {
|
||||
private PayOrderNotifyRespDTO parseOrderNotifyV2(PayNotifyReqDTO data) throws WxPayException {
|
||||
WxPayOrderNotifyResult notifyResult = client.parseOrderNotifyResult(data.getBody());
|
||||
Assert.isTrue(Objects.equals(notifyResult.getResultCode(), "SUCCESS"), "支付结果非 SUCCESS");
|
||||
// 转换结果
|
||||
|
@ -168,20 +168,12 @@ public class WXNativePayClient extends AbstractPayClient<WXPayClientConfig> {
|
|||
.channelOrderNo(notifyResult.getTransactionId())
|
||||
.channelUserId(notifyResult.getOpenid())
|
||||
.successTime(LocalDateTimeUtil.parse(notifyResult.getTimeEnd(), "yyyyMMddHHmmss"))
|
||||
.data(data.getBody())
|
||||
.build();
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public PayRefundNotifyDTO parseRefundNotify(PayNotifyDataDTO notifyData) {
|
||||
// TODO 需要实现
|
||||
throw new UnsupportedOperationException("需要实现");
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected PayCommonResult<PayRefundUnifiedRespDTO> doUnifiedRefund(PayRefundUnifiedReqDTO reqDTO) throws Throwable {
|
||||
protected PayRefundUnifiedRespDTO doUnifiedRefund(PayRefundUnifiedReqDTO reqDTO) {
|
||||
// TODO 需要实现
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
|
|
@ -7,9 +7,12 @@ import cn.hutool.core.lang.Assert;
|
|||
import cn.hutool.core.map.MapUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.iocoder.yudao.framework.common.util.io.FileUtils;
|
||||
import cn.iocoder.yudao.framework.common.util.object.ObjectUtils;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.PayCommonResult;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.dto.*;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.dto.notify.PayNotifyReqDTO;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.dto.notify.PayOrderNotifyRespDTO;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderUnifiedReqDTO;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderUnifiedRespDTO;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.dto.refund.PayRefundUnifiedReqDTO;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.dto.refund.PayRefundUnifiedRespDTO;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.impl.AbstractPayClient;
|
||||
import cn.iocoder.yudao.framework.pay.core.enums.PayChannelEnum;
|
||||
import com.github.binarywang.wxpay.bean.notify.WxPayOrderNotifyResult;
|
||||
|
@ -30,10 +33,6 @@ import java.time.LocalDateTime;
|
|||
import java.time.ZoneId;
|
||||
import java.util.Objects;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.util.json.JsonUtils.toJsonString;
|
||||
import static cn.iocoder.yudao.framework.pay.core.client.impl.wx.WXCodeMapping.CODE_SUCCESS;
|
||||
import static cn.iocoder.yudao.framework.pay.core.client.impl.wx.WXCodeMapping.MESSAGE_SUCCESS;
|
||||
|
||||
/**
|
||||
* 微信支付(公众号)的 PayClient 实现类
|
||||
*
|
||||
|
@ -45,7 +44,7 @@ public class WXPubPayClient extends AbstractPayClient<WXPayClientConfig> {
|
|||
private WxPayService client;
|
||||
|
||||
public WXPubPayClient(Long channelId, WXPayClientConfig config) {
|
||||
super(channelId, PayChannelEnum.WX_PUB.getCode(), config, new WXCodeMapping());
|
||||
super(channelId, PayChannelEnum.WX_PUB.getCode(), config);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -70,28 +69,30 @@ public class WXPubPayClient extends AbstractPayClient<WXPayClientConfig> {
|
|||
}
|
||||
|
||||
@Override
|
||||
public PayCommonResult<WxPayMpOrderResult> doUnifiedOrder(PayOrderUnifiedReqDTO reqDTO) {
|
||||
WxPayMpOrderResult response;
|
||||
try {
|
||||
switch (config.getApiVersion()) {
|
||||
case WXPayClientConfig.API_VERSION_V2:
|
||||
response = this.unifiedOrderV2(reqDTO);
|
||||
break;
|
||||
case WXPayClientConfig.API_VERSION_V3:
|
||||
WxPayUnifiedOrderV3Result.JsapiResult responseV3 = this.unifiedOrderV3(reqDTO);
|
||||
// 将 V3 的结果,统一转换成 V2。返回的字段是一致的
|
||||
response = new WxPayMpOrderResult();
|
||||
BeanUtil.copyProperties(responseV3, response, true);
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException(String.format("未知的 API 版本(%s)", config.getApiVersion()));
|
||||
}
|
||||
} catch (WxPayException e) {
|
||||
log.error("[unifiedOrder][request({}) 发起支付失败,原因({})]", toJsonString(reqDTO), e);
|
||||
return PayCommonResult.build(ObjectUtils.defaultIfNull(e.getErrCode(), e.getReturnCode(), "CustomErrorCode"),
|
||||
ObjectUtils.defaultIfNull(e.getErrCodeDes(), e.getCustomErrorMsg()),null, codeMapping);
|
||||
}
|
||||
return PayCommonResult.build(CODE_SUCCESS, MESSAGE_SUCCESS, response, codeMapping);
|
||||
public PayOrderUnifiedRespDTO doUnifiedOrder(PayOrderUnifiedReqDTO reqDTO) {
|
||||
throw new UnsupportedOperationException();
|
||||
//
|
||||
// WxPayMpOrderResult response;
|
||||
// try {
|
||||
// switch (config.getApiVersion()) {
|
||||
// case WXPayClientConfig.API_VERSION_V2:
|
||||
// response = this.unifiedOrderV2(reqDTO);
|
||||
// break;
|
||||
// case WXPayClientConfig.API_VERSION_V3:
|
||||
// WxPayUnifiedOrderV3Result.JsapiResult responseV3 = this.unifiedOrderV3(reqDTO);
|
||||
// // 将 V3 的结果,统一转换成 V2。返回的字段是一致的
|
||||
// response = new WxPayMpOrderResult();
|
||||
// BeanUtil.copyProperties(responseV3, response, true);
|
||||
// break;
|
||||
// default:
|
||||
// throw new IllegalArgumentException(String.format("未知的 API 版本(%s)", config.getApiVersion()));
|
||||
// }
|
||||
// } catch (WxPayException e) {
|
||||
// log.error("[unifiedOrder][request({}) 发起支付失败,原因({})]", toJsonString(reqDTO), e);
|
||||
// return PayCommonResult.build(ObjectUtils.defaultIfNull(e.getErrCode(), e.getReturnCode(), "CustomErrorCode"),
|
||||
// ObjectUtils.defaultIfNull(e.getErrCodeDes(), e.getCustomErrorMsg()),null, codeMapping);
|
||||
// }
|
||||
// return PayCommonResult.build(CODE_SUCCESS, MESSAGE_SUCCESS, response, codeMapping);
|
||||
}
|
||||
|
||||
|
||||
|
@ -140,8 +141,8 @@ public class WXPubPayClient extends AbstractPayClient<WXPayClientConfig> {
|
|||
* @return 支付回调对象
|
||||
* @throws WxPayException 微信异常类
|
||||
*/
|
||||
@Override
|
||||
public PayOrderNotifyRespDTO parseOrderNotify(PayNotifyDataDTO data) throws WxPayException {
|
||||
// @Override
|
||||
public PayOrderNotifyRespDTO parseOrderNotify(PayNotifyReqDTO data) throws WxPayException {
|
||||
log.info("[parseOrderNotify][微信支付回调data数据: {}]", data.getBody());
|
||||
// 微信支付 v2 回调结果处理
|
||||
switch (config.getApiVersion()) {
|
||||
|
@ -154,7 +155,7 @@ public class WXPubPayClient extends AbstractPayClient<WXPayClientConfig> {
|
|||
}
|
||||
}
|
||||
|
||||
private PayOrderNotifyRespDTO parseOrderNotifyV3(PayNotifyDataDTO data) throws WxPayException {
|
||||
private PayOrderNotifyRespDTO parseOrderNotifyV3(PayNotifyReqDTO data) throws WxPayException {
|
||||
WxPayOrderNotifyV3Result wxPayOrderNotifyV3Result = client.parseOrderNotifyV3Result(data.getBody(), null);
|
||||
WxPayOrderNotifyV3Result.DecryptNotifyResult result = wxPayOrderNotifyV3Result.getResult();
|
||||
// 转换结果
|
||||
|
@ -165,11 +166,10 @@ public class WXPubPayClient extends AbstractPayClient<WXPayClientConfig> {
|
|||
.orderExtensionNo(result.getOutTradeNo())
|
||||
.channelOrderNo(result.getTradeState())
|
||||
.successTime(LocalDateTimeUtil.parse(result.getSuccessTime(), "yyyy-MM-dd'T'HH:mm:ssXXX"))
|
||||
.data(data.getBody())
|
||||
.build();
|
||||
}
|
||||
|
||||
private PayOrderNotifyRespDTO parseOrderNotifyV2(PayNotifyDataDTO data) throws WxPayException {
|
||||
private PayOrderNotifyRespDTO parseOrderNotifyV2(PayNotifyReqDTO data) throws WxPayException {
|
||||
WxPayOrderNotifyResult notifyResult = client.parseOrderNotifyResult(data.getBody());
|
||||
Assert.isTrue(Objects.equals(notifyResult.getResultCode(), "SUCCESS"), "支付结果非 SUCCESS");
|
||||
// 转换结果
|
||||
|
@ -179,19 +179,12 @@ public class WXPubPayClient extends AbstractPayClient<WXPayClientConfig> {
|
|||
.channelOrderNo(notifyResult.getTransactionId())
|
||||
.channelUserId(notifyResult.getOpenid())
|
||||
.successTime(LocalDateTimeUtil.parse(notifyResult.getTimeEnd(), "yyyyMMddHHmmss"))
|
||||
.data(data.getBody())
|
||||
.build();
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public PayRefundNotifyDTO parseRefundNotify(PayNotifyDataDTO notifyData) {
|
||||
// TODO 需要实现
|
||||
throw new UnsupportedOperationException("需要实现");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected PayCommonResult<PayRefundUnifiedRespDTO> doUnifiedRefund(PayRefundUnifiedReqDTO reqDTO) throws Throwable {
|
||||
protected PayRefundUnifiedRespDTO doUnifiedRefund(PayRefundUnifiedReqDTO reqDTO) throws Throwable {
|
||||
// TODO 需要实现
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
|
|
@ -25,7 +25,8 @@ public enum PayChannelEnum {
|
|||
ALIPAY_PC("alipay_pc", "支付宝 PC 网站支付", AlipayPayClientConfig.class),
|
||||
ALIPAY_WAP("alipay_wap", "支付宝 Wap 网站支付", AlipayPayClientConfig.class),
|
||||
ALIPAY_APP("alipay_app", "支付宝App 支付", AlipayPayClientConfig.class),
|
||||
ALIPAY_QR("alipay_qr", "支付宝扫码支付", AlipayPayClientConfig.class);
|
||||
ALIPAY_QR("alipay_qr", "支付宝扫码支付", AlipayPayClientConfig.class),
|
||||
ALIPAY_BAR("alipay_bar", "支付宝条码支付", AlipayPayClientConfig.class);
|
||||
|
||||
/**
|
||||
* 编码
|
||||
|
|
|
@ -0,0 +1,29 @@
|
|||
package cn.iocoder.yudao.framework.pay.core.enums;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* 支付 UI 展示模式
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public enum PayDisplayModeEnum {
|
||||
|
||||
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"), // 应用
|
||||
;
|
||||
|
||||
/**
|
||||
* 展示模式
|
||||
*/
|
||||
private final String mode;
|
||||
|
||||
}
|
|
@ -5,7 +5,7 @@ import cn.iocoder.yudao.framework.common.exception.ErrorCode;
|
|||
/**
|
||||
* 支付框架的错误码枚举
|
||||
*
|
||||
* 短信框架,使用 2-002-000-000 段
|
||||
* 支付框架,使用 2-002-000-000 段
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
|
@ -14,14 +14,16 @@ public interface PayFrameworkErrorCodeConstants {
|
|||
ErrorCode PAY_UNKNOWN = new ErrorCode(2002000000, "未知错误,需要解析");
|
||||
|
||||
// ========== 配置相关相关 2002000100 ==========
|
||||
// todo 芋艿:如下的错误码,怎么处理掉
|
||||
ErrorCode PAY_CONFIG_APP_ID_ERROR = new ErrorCode(2002000100, "支付渠道 AppId 不正确");
|
||||
ErrorCode PAY_CONFIG_SIGN_ERROR = new ErrorCode(2002000100, "签名错误"); // 例如说,微信支付,配置错了 mchId 或者 mchKey
|
||||
|
||||
|
||||
// ========== 其它相关 2002000900 开头 ==========
|
||||
// todo 芋艿:如下的错误码,怎么处理掉
|
||||
ErrorCode PAY_OPENID_ERROR = new ErrorCode(2002000900, "无效的 openid"); // 例如说,微信 openid 未授权过
|
||||
ErrorCode PAY_PARAM_MISSING = new ErrorCode(2002000901, "请求参数缺失"); // 例如说,支付少传了金额
|
||||
|
||||
ErrorCode EXCEPTION = new ErrorCode(2002000999, "调用异常");
|
||||
ErrorCode PAY_EXCEPTION = new ErrorCode(2002000999, "调用异常");
|
||||
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@ package cn.iocoder.yudao.framework.pay.core.enums;
|
|||
* @author jason
|
||||
*/
|
||||
public enum PayNotifyRefundStatusEnum {
|
||||
|
||||
/**
|
||||
* 支付宝 中 全额退款 trade_status=TRADE_CLOSED, 部分退款 trade_status=TRADE_SUCCESS
|
||||
* 退款成功
|
||||
|
@ -17,4 +18,5 @@ public enum PayNotifyRefundStatusEnum {
|
|||
* 退款异常
|
||||
*/
|
||||
ABNORMAL;
|
||||
|
||||
}
|
||||
|
|
|
@ -5,7 +5,7 @@ import cn.hutool.core.util.RandomUtil;
|
|||
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
|
||||
import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.PayClient;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.dto.PayOrderUnifiedReqDTO;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderUnifiedReqDTO;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.impl.alipay.AlipayPayClientConfig;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.impl.alipay.AlipayQrPayClient;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.impl.alipay.AlipayWapPayClient;
|
||||
|
@ -46,8 +46,8 @@ public class PayClientFactoryImplIntegrationTest {
|
|||
PayClient client = payClientFactory.getPayClient(channelId);
|
||||
// 发起支付
|
||||
PayOrderUnifiedReqDTO reqDTO = buildPayOrderUnifiedReqDTO();
|
||||
CommonResult<?> result = client.unifiedOrder(reqDTO);
|
||||
System.out.println(result);
|
||||
// CommonResult<?> result = client.unifiedOrder(reqDTO);
|
||||
// System.out.println(result);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -69,8 +69,8 @@ public class PayClientFactoryImplIntegrationTest {
|
|||
PayClient client = payClientFactory.getPayClient(channelId);
|
||||
// 发起支付
|
||||
PayOrderUnifiedReqDTO reqDTO = buildPayOrderUnifiedReqDTO();
|
||||
CommonResult<?> result = client.unifiedOrder(reqDTO);
|
||||
System.out.println(result);
|
||||
// CommonResult<?> result = client.unifiedOrder(reqDTO);
|
||||
// System.out.println(result);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -93,9 +93,9 @@ public class PayClientFactoryImplIntegrationTest {
|
|||
// 发起支付
|
||||
PayOrderUnifiedReqDTO reqDTO = buildPayOrderUnifiedReqDTO();
|
||||
reqDTO.setNotifyUrl("http://yunai.natapp1.cc/admin-api/pay/notify/callback/18"); // TODO @tina: 这里改成你的 natapp 回调地址
|
||||
CommonResult<AlipayTradePrecreateResponse> result = (CommonResult<AlipayTradePrecreateResponse>) client.unifiedOrder(reqDTO);
|
||||
System.out.println(JsonUtils.toJsonString(result));
|
||||
System.out.println(result.getData().getQrCode());
|
||||
// CommonResult<AlipayTradePrecreateResponse> result = (CommonResult<AlipayTradePrecreateResponse>) client.unifiedOrder(reqDTO);
|
||||
// System.out.println(JsonUtils.toJsonString(result));
|
||||
// System.out.println(result.getData().getQrCode());
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -116,8 +116,8 @@ public class PayClientFactoryImplIntegrationTest {
|
|||
PayClient client = payClientFactory.getPayClient(channelId);
|
||||
// 发起支付
|
||||
PayOrderUnifiedReqDTO reqDTO = buildPayOrderUnifiedReqDTO();
|
||||
CommonResult<?> result = client.unifiedOrder(reqDTO);
|
||||
System.out.println(JsonUtils.toJsonString(result));
|
||||
// CommonResult<?> result = client.unifiedOrder(reqDTO);
|
||||
// System.out.println(JsonUtils.toJsonString(result));
|
||||
}
|
||||
|
||||
private static PayOrderUnifiedReqDTO buildPayOrderUnifiedReqDTO() {
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
package cn.iocoder.yudao.framework.pay.core.client.impl.alipay;
|
||||
import cn.hutool.core.util.ReflectUtil;
|
||||
import cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.PayCommonResult;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.dto.PayOrderUnifiedReqDTO;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderUnifiedReqDTO;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderUnifiedRespDTO;
|
||||
import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest;
|
||||
import com.alipay.api.AlipayApiException;
|
||||
import com.alipay.api.DefaultAlipayClient;
|
||||
|
@ -87,13 +87,13 @@ public class AlipayQrPayClientTest extends BaseMockitoUnitTest {
|
|||
}))).thenReturn(response);
|
||||
|
||||
|
||||
PayCommonResult<AlipayTradePrecreateResponse> result = client.doUnifiedOrder(reqDTO);
|
||||
// 断言
|
||||
assertEquals(response.getCode(), result.getApiCode());
|
||||
assertEquals(response.getMsg(), result.getApiMsg());
|
||||
// TODO @tina:这个断言木有过?
|
||||
assertEquals(GlobalErrorCodeConstants.SUCCESS.getCode(), result.getCode());
|
||||
assertEquals(GlobalErrorCodeConstants.SUCCESS.getMsg(), result.getMsg());
|
||||
// PayCommonResult<PayOrderUnifiedRespDTO> result = client.doUnifiedOrder(reqDTO);
|
||||
// // 断言
|
||||
// assertEquals(response.getCode(), result.getApiCode());
|
||||
// assertEquals(response.getMsg(), result.getApiMsg());
|
||||
// // TODO @tina:这个断言木有过?
|
||||
// assertEquals(GlobalErrorCodeConstants.SUCCESS.getCode(), result.getCode());
|
||||
// assertEquals(GlobalErrorCodeConstants.SUCCESS.getMsg(), result.getMsg());
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package cn.iocoder.yudao.module.pay.api.notify.dto;
|
||||
|
||||
import cn.iocoder.yudao.module.pay.enums.refund.PayRefundStatusEnum;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
|
@ -31,12 +32,4 @@ public class PayRefundNotifyReqDTO {
|
|||
@NotNull(message = "支付退款编号不能为空")
|
||||
private Long payRefundId;
|
||||
|
||||
/**
|
||||
* 退款状态
|
||||
*
|
||||
* (成功,失败) TODO 芋艿:枚举
|
||||
*/
|
||||
@NotNull(message = "退款状态不能为空")
|
||||
private Integer status;
|
||||
|
||||
}
|
||||
|
|
|
@ -28,12 +28,6 @@ public class PayRefundCreateReqDTO {
|
|||
|
||||
// ========== 商户相关字段 ==========
|
||||
|
||||
/**
|
||||
* 商户订单编号
|
||||
*/
|
||||
@NotEmpty(message = "商户订单编号不能为空")
|
||||
private String merchantOrderId;
|
||||
|
||||
/**
|
||||
* 退款描述
|
||||
*/
|
||||
|
@ -43,6 +37,12 @@ public class PayRefundCreateReqDTO {
|
|||
|
||||
// ========== 订单相关字段 ==========
|
||||
|
||||
/**
|
||||
* 支付单号
|
||||
*/
|
||||
@NotNull(message = "支付单号不能为空")
|
||||
private Long payOrderId;
|
||||
|
||||
/**
|
||||
* 退款金额,单位:分
|
||||
*/
|
||||
|
|
|
@ -27,8 +27,16 @@ public class PayRefundRespDTO {
|
|||
* 枚举 {@link PayRefundStatusEnum}
|
||||
*/
|
||||
private Integer status;
|
||||
/**
|
||||
* 退款金额,单位:分
|
||||
*/
|
||||
private Integer refundAmount;
|
||||
|
||||
// ========== 渠道相关字段 ==========
|
||||
// ========== 商户相关字段 ==========
|
||||
/**
|
||||
* 商户订单编号
|
||||
*/
|
||||
private String merchantOrderId;
|
||||
/**
|
||||
* 退款成功时间
|
||||
*/
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package cn.iocoder.yudao.module.pay.enums;
|
||||
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import cn.iocoder.yudao.framework.common.exception.ErrorCode;
|
||||
|
||||
/**
|
||||
|
@ -50,11 +51,23 @@ public interface ErrorCodeConstants {
|
|||
ErrorCode PAY_REFUND_SUCCEED = new ErrorCode(1007006003, "已经退款成功");
|
||||
ErrorCode PAY_REFUND_NOT_FOUND = new ErrorCode(1007006004, "支付退款单不存在");
|
||||
|
||||
|
||||
/**
|
||||
* ========== 支付商户信息 1-007-004-000 ==========
|
||||
*/
|
||||
ErrorCode PAY_MERCHANT_NOT_EXISTS = new ErrorCode(1007004000, "支付商户信息不存在");
|
||||
ErrorCode PAY_MERCHANT_EXIST_APP_CANT_DELETE = new ErrorCode(1007004001, "支付商户存在支付应用,无法删除");
|
||||
|
||||
// ========== 示例订单 1-007-900-000 ==========
|
||||
ErrorCode PAY_DEMO_ORDER_NOT_FOUND = new ErrorCode(100790000, "示例订单不存在");
|
||||
ErrorCode PAY_DEMO_ORDER_UPDATE_PAID_STATUS_NOT_UNPAID = new ErrorCode(100790001, "示例订单更新支付状态失败,订单不是【未支付】状态");
|
||||
ErrorCode PAY_DEMO_ORDER_UPDATE_PAID_FAIL_PAY_ORDER_ID_ERROR = new ErrorCode(100790002, "示例订单更新支付状态失败,支付单编号不匹配");
|
||||
ErrorCode PAY_DEMO_ORDER_UPDATE_PAID_FAIL_PAY_ORDER_STATUS_NOT_SUCCESS = new ErrorCode(100790003, "示例订单更新支付状态失败,支付单状态不是【支付成功】状态");
|
||||
ErrorCode PAY_DEMO_ORDER_UPDATE_PAID_FAIL_PAY_PRICE_NOT_MATCH = new ErrorCode(100790004, "示例订单更新支付状态失败,支付单金额不匹配");
|
||||
ErrorCode PAY_DEMO_ORDER_REFUND_FAIL_NOT_PAID = new ErrorCode(100790005, "发起退款失败,示例订单未支付");
|
||||
ErrorCode PAY_DEMO_ORDER_REFUND_FAIL_REFUNDED = new ErrorCode(100790006, "发起退款失败,示例订单已退款");
|
||||
ErrorCode PAY_DEMO_ORDER_REFUND_FAIL_REFUND_NOT_FOUND = new ErrorCode(100790007, "发起退款失败,退款订单不存在");
|
||||
ErrorCode PAY_DEMO_ORDER_REFUND_FAIL_REFUND_NOT_SUCCESS = new ErrorCode(100790008, "发起退款失败,退款订单未退款成功");
|
||||
ErrorCode PAY_DEMO_ORDER_REFUND_FAIL_REFUND_ORDER_ID_ERROR = new ErrorCode(100790008, "发起退款失败,退款单编号不匹配");
|
||||
ErrorCode PAY_DEMO_ORDER_REFUND_FAIL_REFUND_PRICE_NOT_MATCH = new ErrorCode(100790004, "发起退款失败,退款单金额不匹配");
|
||||
|
||||
}
|
||||
|
|
|
@ -2,9 +2,12 @@ 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 cn.iocoder.yudao.module.pay.service.refund.PayRefundService;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
|
||||
/**
|
||||
* 退款单 API 实现类
|
||||
*
|
||||
|
@ -14,10 +17,12 @@ import org.springframework.validation.annotation.Validated;
|
|||
@Validated
|
||||
public class PayRefundApiImpl implements PayRefundApi {
|
||||
|
||||
@Resource
|
||||
private PayRefundService payRefundService;
|
||||
|
||||
@Override
|
||||
public Long createPayRefund(PayRefundCreateReqDTO reqDTO) {
|
||||
// TODO 芋艿:暂未实现
|
||||
return null;
|
||||
return payRefundService.createPayRefund(reqDTO);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -3,20 +3,26 @@ 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.operatelog.core.annotations.OperateLog;
|
||||
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.PayDemoOrderCreateReqVO;
|
||||
import cn.iocoder.yudao.module.pay.controller.admin.demo.vo.PayDemoOrderRespVO;
|
||||
import cn.iocoder.yudao.module.pay.convert.demo.PayDemoOrderConvert;
|
||||
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 org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import javax.annotation.security.PermitAll;
|
||||
import javax.validation.Valid;
|
||||
|
||||
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 = "管理后台 - 示例订单")
|
||||
|
@ -41,4 +47,32 @@ public class PayDemoOrderController {
|
|||
return success(PayDemoOrderConvert.INSTANCE.convertPage(pageResult));
|
||||
}
|
||||
|
||||
@PostMapping("/update-paid")
|
||||
@Operation(summary = "更新示例订单为已支付") // 由 pay-module 支付服务,进行回调,可见 PayNotifyJob
|
||||
@PermitAll // 无需登录,安全由 PayDemoOrderService 内部校验实现
|
||||
@OperateLog(enable = false) // 禁用操作日志,因为没有操作人
|
||||
public CommonResult<Boolean> 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<Boolean> refundDemoOrder(@RequestParam("id") Long id) {
|
||||
payDemoOrderService.refundDemoOrder(id, getClientIP());
|
||||
return success(true);
|
||||
}
|
||||
|
||||
@PostMapping("/update-refunded")
|
||||
@Operation(summary = "更新示例订单为已退款") // 由 pay-module 支付服务,进行回调,可见 PayNotifyJob
|
||||
@PermitAll // 无需登录,安全由 PayDemoOrderService 内部校验实现
|
||||
@OperateLog(enable = false) // 禁用操作日志,因为没有操作人
|
||||
public CommonResult<Boolean> updateDemoOrderRefunded(@RequestBody PayRefundNotifyReqDTO notifyReqDTO) {
|
||||
payDemoOrderService.updateDemoOrderRefunded(Long.valueOf(notifyReqDTO.getMerchantOrderId()),
|
||||
notifyReqDTO.getPayRefundId());
|
||||
return success(true);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -12,6 +12,9 @@ import java.time.LocalDateTime;
|
|||
@Data
|
||||
public class PayDemoOrderRespVO {
|
||||
|
||||
@Schema(description = "订单编号", required = true, example = "1024")
|
||||
private Long id;
|
||||
|
||||
@Schema(description = "用户编号", required = true, example = "23199")
|
||||
private Long userId;
|
||||
|
||||
|
@ -33,10 +36,19 @@ public class PayDemoOrderRespVO {
|
|||
@Schema(description = "订单支付时间")
|
||||
private LocalDateTime payTime;
|
||||
|
||||
@Schema(description = "支付渠道", example = "alipay_qr")
|
||||
private String payChannelCode;
|
||||
|
||||
@Schema(description = "支付退款编号", example = "23366")
|
||||
private Long payRefundId;
|
||||
|
||||
@Schema(description = "退款金额,单位:分", required = true, example = "14039")
|
||||
private Integer refundPrice;
|
||||
|
||||
@Schema(description = "退款时间")
|
||||
private LocalDateTime refundTime;
|
||||
|
||||
@Schema(description = "创建时间", required = true)
|
||||
private LocalDateTime createTime;
|
||||
|
||||
}
|
||||
|
|
|
@ -3,7 +3,11 @@ package cn.iocoder.yudao.module.pay.controller.admin.notify;
|
|||
import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.PayClient;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.PayClientFactory;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.dto.PayNotifyDataDTO;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.dto.notify.PayNotifyReqDTO;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.dto.notify.PayOrderNotifyRespDTO;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.dto.notify.PayRefundNotifyRespDTO;
|
||||
import cn.iocoder.yudao.module.pay.dal.dataobject.merchant.PayChannelDO;
|
||||
import cn.iocoder.yudao.module.pay.service.merchant.PayChannelService;
|
||||
import cn.iocoder.yudao.module.pay.service.order.PayOrderService;
|
||||
import cn.iocoder.yudao.module.pay.service.refund.PayRefundService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
|
@ -17,6 +21,7 @@ import javax.annotation.security.PermitAll;
|
|||
import java.util.Map;
|
||||
|
||||
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.PAY_CHANNEL_CLIENT_NOT_FOUND;
|
||||
|
||||
@Tag(name = "管理后台 - 支付通知")
|
||||
|
@ -64,28 +69,32 @@ public class PayNotifyController {
|
|||
@PermitAll
|
||||
@OperateLog(enable = false) // 回调地址,无需记录操作日志
|
||||
public String notifyCallback(@PathVariable("channelId") Long channelId,
|
||||
@RequestParam Map<String, String> params,
|
||||
@RequestBody String body) throws Exception {
|
||||
// 校验支付渠道是否存在
|
||||
@RequestParam(required = false) Map<String, String> params,
|
||||
@RequestBody(required = false) String body) {
|
||||
log.info("[notifyCallback][channelId({}) 回调数据({}/{})]", channelId, params, body);
|
||||
// 1. 校验支付渠道是否存在
|
||||
PayClient payClient = payClientFactory.getPayClient(channelId);
|
||||
if (payClient == null) {
|
||||
log.error("[notifyCallback][渠道编号({}) 找不到对应的支付客户端]", channelId);
|
||||
throw exception(PAY_CHANNEL_CLIENT_NOT_FOUND);
|
||||
}
|
||||
// 校验通知数据是否合法
|
||||
PayNotifyDataDTO notifyData = PayNotifyDataDTO.builder().params(params).body(body).build();
|
||||
payClient.verifyNotifyData(notifyData);
|
||||
|
||||
// 情况一:如果是退款,则发起退款通知
|
||||
if (payClient.isRefundNotify(notifyData)) {
|
||||
refundService.notifyPayRefund(channelId, PayNotifyDataDTO.builder().params(params).body(body).build());
|
||||
// 2. 解析通知数据
|
||||
PayNotifyReqDTO rawNotify = PayNotifyReqDTO.builder().params(params).body(body).build();
|
||||
Object notify = payClient.parseNotify(rawNotify);
|
||||
|
||||
// 3. 处理通知
|
||||
// 3.1:退款通知
|
||||
if (notify instanceof PayRefundNotifyRespDTO) {
|
||||
refundService.notifyPayRefund(channelId, (PayRefundNotifyRespDTO) notify, rawNotify);
|
||||
return "success";
|
||||
}
|
||||
|
||||
// 情况二:如果非退款,则发起支付通知
|
||||
orderService.notifyPayOrder(channelId, PayNotifyDataDTO.builder().params(params).body(body).build());
|
||||
// 3.2:支付通知
|
||||
if (notify instanceof PayOrderNotifyRespDTO) {
|
||||
orderService.notifyPayOrder(channelId, (PayOrderNotifyRespDTO) notify, rawNotify);
|
||||
return "success";
|
||||
}
|
||||
|
||||
throw new UnsupportedOperationException("未知通知:" + toJsonString(notify));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -2,6 +2,12 @@ package cn.iocoder.yudao.module.pay.controller.admin.order;
|
|||
|
||||
import cn.hutool.core.collection.CollectionUtil;
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
|
||||
import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils;
|
||||
import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog;
|
||||
import cn.iocoder.yudao.framework.pay.core.enums.PayChannelEnum;
|
||||
import cn.iocoder.yudao.module.pay.controller.admin.order.vo.*;
|
||||
import cn.iocoder.yudao.module.pay.convert.order.PayOrderConvert;
|
||||
import cn.iocoder.yudao.module.pay.dal.dataobject.merchant.PayAppDO;
|
||||
|
@ -12,21 +18,12 @@ import cn.iocoder.yudao.module.pay.service.merchant.PayAppService;
|
|||
import cn.iocoder.yudao.module.pay.service.merchant.PayMerchantService;
|
||||
import cn.iocoder.yudao.module.pay.service.order.PayOrderExtensionService;
|
||||
import cn.iocoder.yudao.module.pay.service.order.PayOrderService;
|
||||
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
|
||||
import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils;
|
||||
import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog;
|
||||
import cn.iocoder.yudao.framework.pay.core.enums.PayChannelEnum;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
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.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
@ -37,6 +34,7 @@ import java.util.List;
|
|||
import java.util.Map;
|
||||
|
||||
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.operatelog.core.enums.OperateTypeEnum.EXPORT;
|
||||
|
||||
@Tag(name = "管理后台 - 支付订单")
|
||||
|
@ -46,7 +44,7 @@ import static cn.iocoder.yudao.framework.operatelog.core.enums.OperateTypeEnum.E
|
|||
public class PayOrderController {
|
||||
|
||||
@Resource
|
||||
private PayOrderService orderService;
|
||||
private PayOrderService payOrderService;
|
||||
@Resource
|
||||
private PayOrderExtensionService orderExtensionService;
|
||||
@Resource
|
||||
|
@ -58,8 +56,17 @@ public class PayOrderController {
|
|||
@Operation(summary = "获得支付订单")
|
||||
@Parameter(name = "id", description = "编号", required = true, example = "1024")
|
||||
@PreAuthorize("@ss.hasPermission('pay:order:query')")
|
||||
public CommonResult<PayOrderDetailsRespVO> getOrder(@RequestParam("id") Long id) {
|
||||
PayOrderDO order = orderService.getOrder(id);
|
||||
public CommonResult<PayOrderRespVO> getOrder(@RequestParam("id") Long id) {
|
||||
return success(PayOrderConvert.INSTANCE.convert(payOrderService.getOrder(id)));
|
||||
}
|
||||
|
||||
// TODO 芋艿:看看怎么优化下;
|
||||
@GetMapping("/get-detail")
|
||||
@Operation(summary = "获得支付订单详情")
|
||||
@Parameter(name = "id", description = "编号", required = true, example = "1024")
|
||||
@PreAuthorize("@ss.hasPermission('pay:order:query')")
|
||||
public CommonResult<PayOrderDetailsRespVO> getOrderDetail(@RequestParam("id") Long id) {
|
||||
PayOrderDO order = payOrderService.getOrder(id);
|
||||
if (ObjectUtil.isNull(order)) {
|
||||
return success(new PayOrderDetailsRespVO());
|
||||
}
|
||||
|
@ -82,11 +89,18 @@ public class PayOrderController {
|
|||
return success(respVO);
|
||||
}
|
||||
|
||||
@PostMapping("/submit")
|
||||
@Operation(summary = "提交支付订单")
|
||||
public CommonResult<PayOrderSubmitRespVO> submitPayOrder(@RequestBody PayOrderSubmitReqVO reqVO) {
|
||||
PayOrderSubmitRespVO respVO = payOrderService.submitPayOrder(reqVO, getClientIP());
|
||||
return success(respVO);
|
||||
}
|
||||
|
||||
@GetMapping("/page")
|
||||
@Operation(summary = "获得支付订单分页")
|
||||
@PreAuthorize("@ss.hasPermission('pay:order:query')")
|
||||
public CommonResult<PageResult<PayOrderPageItemRespVO>> getOrderPage(@Valid PayOrderPageReqVO pageVO) {
|
||||
PageResult<PayOrderDO> pageResult = orderService.getOrderPage(pageVO);
|
||||
PageResult<PayOrderDO> pageResult = payOrderService.getOrderPage(pageVO);
|
||||
if (CollectionUtil.isEmpty(pageResult.getList())) {
|
||||
return success(new PageResult<>(pageResult.getTotal()));
|
||||
}
|
||||
|
@ -120,7 +134,7 @@ public class PayOrderController {
|
|||
public void exportOrderExcel(@Valid PayOrderExportReqVO exportReqVO,
|
||||
HttpServletResponse response) throws IOException {
|
||||
|
||||
List<PayOrderDO> list = orderService.getOrderList(exportReqVO);
|
||||
List<PayOrderDO> list = payOrderService.getOrderList(exportReqVO);
|
||||
if (CollectionUtil.isEmpty(list)) {
|
||||
ExcelUtils.write(response, "支付订单.xls", "数据",
|
||||
PayOrderExcelVO.class, new ArrayList<>());
|
||||
|
|
|
@ -0,0 +1,29 @@
|
|||
package cn.iocoder.yudao.module.pay.controller.admin.order.vo;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
import javax.validation.constraints.NotEmpty;
|
||||
import javax.validation.constraints.NotNull;
|
||||
import java.awt.*;
|
||||
import java.util.Map;
|
||||
|
||||
@Schema(description = "管理后台 - 支付订单提交 Request VO")
|
||||
@Data
|
||||
public class PayOrderSubmitReqVO {
|
||||
|
||||
@Schema(description = "支付单编号", required = true, example = "1024")
|
||||
@NotNull(message = "支付单编号不能为空")
|
||||
private Long id;
|
||||
|
||||
@Schema(description = "支付渠道", required = true, example = "wx_pub")
|
||||
@NotEmpty(message = "支付渠道不能为空")
|
||||
private String channelCode;
|
||||
|
||||
@Schema(description = "支付渠道的额外参数,例如说,微信公众号需要传递 openid 参数")
|
||||
private Map<String, String> channelExtras;
|
||||
|
||||
@Schema(description = "展示模式", example = "url") // 参见 {@link PayDisplayModeEnum} 枚举。如果不传递,则每个支付渠道使用默认的方式
|
||||
private String displayMode;
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
package cn.iocoder.yudao.module.pay.controller.admin.order.vo;
|
||||
|
||||
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 = "管理后台 - 支付订单提交 Response VO")
|
||||
@Data
|
||||
public class PayOrderSubmitRespVO {
|
||||
|
||||
@Schema(description = "展示模式", required = true, example = "url") // 参见 PayDisplayModeEnum 枚举
|
||||
private String displayMode;
|
||||
|
||||
@Schema(description = "展示内容", required = true)
|
||||
private String displayContent;
|
||||
|
||||
}
|
|
@ -1,15 +1,13 @@
|
|||
package cn.iocoder.yudao.module.pay.controller.app.order;
|
||||
|
||||
import cn.hutool.core.bean.BeanUtil;
|
||||
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
|
||||
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.dal.dataobject.order.PayOrderDO;
|
||||
import cn.iocoder.yudao.module.pay.convert.order.PayOrderConvert;
|
||||
import cn.iocoder.yudao.module.pay.service.order.PayOrderService;
|
||||
import cn.iocoder.yudao.module.pay.service.order.dto.PayOrderSubmitReqDTO;
|
||||
import cn.iocoder.yudao.module.pay.service.order.dto.PayOrderSubmitRespDTO;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
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.PostMapping;
|
||||
|
@ -34,20 +32,9 @@ public class AppPayOrderController {
|
|||
|
||||
@PostMapping("/submit")
|
||||
@Operation(summary = "提交支付订单")
|
||||
// @PreAuthenticated // TODO 暂时不加登陆验证,前端暂时没做好
|
||||
public CommonResult<AppPayOrderSubmitRespVO> submitPayOrder(@RequestBody AppPayOrderSubmitReqVO reqVO) {
|
||||
// 获得订单
|
||||
PayOrderDO payOrder = orderService.getOrder(reqVO.getId());
|
||||
|
||||
// 提交支付
|
||||
PayOrderSubmitReqDTO reqDTO = new PayOrderSubmitReqDTO();
|
||||
BeanUtil.copyProperties(reqVO, reqDTO, false);
|
||||
reqDTO.setUserIp(getClientIP());
|
||||
reqDTO.setAppId(payOrder.getAppId());
|
||||
PayOrderSubmitRespDTO respDTO = orderService.submitPayOrder(reqDTO);
|
||||
|
||||
// 拼接返回
|
||||
return success(AppPayOrderSubmitRespVO.builder().invokeResponse(respDTO.getInvokeResponse()).build());
|
||||
PayOrderSubmitRespVO respVO = orderService.submitPayOrder(reqVO, getClientIP());
|
||||
return success(PayOrderConvert.INSTANCE.convert3(respVO));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
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;
|
||||
|
@ -10,18 +11,5 @@ import java.util.Map;
|
|||
|
||||
@Schema(description = "用户 APP - 支付订单提交 Request VO")
|
||||
@Data
|
||||
@Accessors(chain = true)
|
||||
public class AppPayOrderSubmitReqVO {
|
||||
|
||||
@Schema(description = "支付单编号", required = true, example = "1024")
|
||||
@NotNull(message = "支付单编号不能为空")
|
||||
private Long id;
|
||||
|
||||
@Schema(description = "支付渠道", required = true, example = "wx_pub")
|
||||
@NotEmpty(message = "支付渠道不能为空")
|
||||
private String channelCode;
|
||||
|
||||
@Schema(description = "支付渠道的额外参数,例如说,微信公众号需要传递 openid 参数")
|
||||
private Map<String, String> channelExtras;
|
||||
|
||||
public class AppPayOrderSubmitReqVO extends PayOrderSubmitReqVO {
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
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;
|
||||
|
@ -9,15 +10,6 @@ import lombok.experimental.Accessors;
|
|||
|
||||
@Schema(description = "用户 APP - 支付订单提交 Response VO")
|
||||
@Data
|
||||
@Accessors(chain = true)
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class AppPayOrderSubmitRespVO {
|
||||
|
||||
/**
|
||||
* 调用支付渠道的响应结果
|
||||
*/
|
||||
private Object invokeResponse;
|
||||
public class AppPayOrderSubmitRespVO extends PayOrderSubmitRespVO {
|
||||
|
||||
}
|
||||
|
|
|
@ -1,47 +0,0 @@
|
|||
package cn.iocoder.yudao.module.pay.controller.app.refund;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
|
||||
import cn.iocoder.yudao.module.pay.controller.app.refund.vo.AppPayRefundReqVO;
|
||||
import cn.iocoder.yudao.module.pay.controller.app.refund.vo.AppPayRefundRespVO;
|
||||
import cn.iocoder.yudao.module.pay.convert.refund.PayRefundConvert;
|
||||
import cn.iocoder.yudao.module.pay.service.order.dto.PayRefundReqDTO;
|
||||
import cn.iocoder.yudao.module.pay.service.refund.PayRefundService;
|
||||
import cn.iocoder.yudao.module.pay.util.PaySeqUtils;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
|
||||
import static cn.iocoder.yudao.framework.common.util.servlet.ServletUtils.getClientIP;
|
||||
|
||||
@Tag(name = "用户 APP - 退款订单")
|
||||
@RestController
|
||||
@RequestMapping("/pay/refund")
|
||||
@Validated
|
||||
@Slf4j
|
||||
public class AppPayRefundController {
|
||||
|
||||
@Resource
|
||||
private PayRefundService refundService;
|
||||
|
||||
@PostMapping("/refund")
|
||||
@Operation(summary = "提交退款订单")
|
||||
public CommonResult<AppPayRefundRespVO> submitRefundOrder(@RequestBody AppPayRefundReqVO reqVO){
|
||||
PayRefundReqDTO req = PayRefundConvert.INSTANCE.convert(reqVO);
|
||||
req.setUserIp(getClientIP());
|
||||
// TODO 测试暂时模拟生成商户退款订单
|
||||
if(StrUtil.isEmpty(reqVO.getMerchantRefundId())) {
|
||||
req.setMerchantRefundId(PaySeqUtils.genMerchantRefundNo());
|
||||
}
|
||||
return success(PayRefundConvert.INSTANCE.convert(refundService.submitRefundOrder(req)));
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
/**
|
||||
* TODO 芋艿:占个位置,没啥用
|
||||
*/
|
||||
package cn.iocoder.yudao.module.pay.controller.app.refund;
|
|
@ -1,34 +0,0 @@
|
|||
package cn.iocoder.yudao.module.pay.controller.app.refund.vo;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import javax.validation.constraints.NotEmpty;
|
||||
import javax.validation.constraints.NotNull;
|
||||
|
||||
@Schema(description = "用户 APP - 退款订单 Req VO")
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class AppPayRefundReqVO {
|
||||
|
||||
@Schema(description = "支付订单编号自增", required = true, example = "10")
|
||||
@NotNull(message = "支付订单编号自增")
|
||||
private Long payOrderId;
|
||||
|
||||
@Schema(description = "退款金额", required = true, example = "1")
|
||||
@NotNull(message = "退款金额")
|
||||
private Long amount;
|
||||
|
||||
@Schema(description = "退款原因", required = true, example = "不喜欢")
|
||||
@NotEmpty(message = "退款原因")
|
||||
private String reason;
|
||||
|
||||
@Schema(description = "商户退款订单号", required = true, example = "MR202111180000000001")
|
||||
//TODO 测试暂时模拟生成
|
||||
//@NotEmpty(message = "商户退款订单号")
|
||||
private String merchantRefundId;
|
||||
|
||||
}
|
|
@ -1,21 +0,0 @@
|
|||
package cn.iocoder.yudao.module.pay.controller.app.refund.vo;
|
||||
|
||||
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
|
||||
@Accessors(chain = true)
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class AppPayRefundRespVO {
|
||||
|
||||
@Schema(description = "退款订单编号", required = true, example = "10")
|
||||
private Long refundId;
|
||||
|
||||
}
|
|
@ -1,16 +1,15 @@
|
|||
package cn.iocoder.yudao.module.pay.convert.order;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.dto.PayOrderUnifiedReqDTO;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderUnifiedReqDTO;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderUnifiedRespDTO;
|
||||
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.PayOrderDetailsRespVO;
|
||||
import cn.iocoder.yudao.module.pay.controller.admin.order.vo.PayOrderExcelVO;
|
||||
import cn.iocoder.yudao.module.pay.controller.admin.order.vo.PayOrderPageItemRespVO;
|
||||
import cn.iocoder.yudao.module.pay.controller.admin.order.vo.PayOrderRespVO;
|
||||
import cn.iocoder.yudao.module.pay.controller.admin.order.vo.*;
|
||||
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.dal.dataobject.order.PayOrderDO;
|
||||
import cn.iocoder.yudao.module.pay.dal.dataobject.order.PayOrderExtensionDO;
|
||||
import cn.iocoder.yudao.module.pay.service.order.dto.PayOrderSubmitReqDTO;
|
||||
import org.mapstruct.Mapper;
|
||||
import org.mapstruct.Mapping;
|
||||
import org.mapstruct.factory.Mappers;
|
||||
|
@ -31,6 +30,8 @@ public interface PayOrderConvert {
|
|||
|
||||
PayOrderRespVO convert(PayOrderDO bean);
|
||||
|
||||
PayOrderRespDTO convert2(PayOrderDO order);
|
||||
|
||||
PayOrderDetailsRespVO orderDetailConvert(PayOrderDO bean);
|
||||
|
||||
PayOrderDetailsRespVO.PayOrderExtension orderDetailExtensionConvert(PayOrderExtensionDO bean);
|
||||
|
@ -88,14 +89,15 @@ public interface PayOrderConvert {
|
|||
return payOrderExcelVO;
|
||||
}
|
||||
|
||||
|
||||
PayOrderDO convert(PayOrderCreateReqDTO bean);
|
||||
|
||||
@Mapping(target = "id", ignore = true)
|
||||
PayOrderExtensionDO convert(PayOrderSubmitReqDTO bean);
|
||||
PayOrderExtensionDO convert(PayOrderSubmitReqVO bean, String userIp);
|
||||
|
||||
PayOrderUnifiedReqDTO convert2(PayOrderSubmitReqDTO bean);
|
||||
PayOrderUnifiedReqDTO convert2(PayOrderSubmitReqVO reqVO);
|
||||
|
||||
PayOrderRespDTO convert2(PayOrderDO bean);
|
||||
PayOrderSubmitRespVO convert(PayOrderUnifiedRespDTO bean);
|
||||
|
||||
AppPayOrderSubmitRespVO convert3(PayOrderSubmitRespVO bean);
|
||||
|
||||
}
|
||||
|
|
|
@ -2,12 +2,8 @@ package cn.iocoder.yudao.module.pay.convert.refund;
|
|||
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.yudao.module.pay.controller.admin.refund.vo.*;
|
||||
import cn.iocoder.yudao.module.pay.controller.app.refund.vo.AppPayRefundReqVO;
|
||||
import cn.iocoder.yudao.module.pay.controller.app.refund.vo.AppPayRefundRespVO;
|
||||
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.service.order.dto.PayRefundReqDTO;
|
||||
import cn.iocoder.yudao.module.pay.service.order.dto.PayRefundRespDTO;
|
||||
import org.mapstruct.Mapper;
|
||||
import org.mapstruct.Mapping;
|
||||
import org.mapstruct.Mappings;
|
||||
|
@ -17,11 +13,6 @@ import java.math.BigDecimal;
|
|||
import java.math.RoundingMode;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 退款订单 Convert
|
||||
*
|
||||
* @author aquan
|
||||
*/
|
||||
@Mapper
|
||||
public interface PayRefundConvert {
|
||||
|
||||
|
@ -102,8 +93,4 @@ public interface PayRefundConvert {
|
|||
})
|
||||
PayRefundDO convert(PayOrderDO orderDO);
|
||||
|
||||
PayRefundReqDTO convert(AppPayRefundReqVO bean);
|
||||
|
||||
AppPayRefundRespVO convert(PayRefundRespDTO bean);
|
||||
|
||||
}
|
||||
|
|
|
@ -73,14 +73,16 @@ public class PayDemoOrderDO extends BaseDO {
|
|||
|
||||
// ========== 退款相关字段 ==========
|
||||
/**
|
||||
* 退款金额
|
||||
* 支付退款单号
|
||||
*/
|
||||
private Long payRefundId;
|
||||
/**
|
||||
* 退款金额,单位:分
|
||||
*/
|
||||
private Integer refundPrice;
|
||||
/**
|
||||
* 退款时间
|
||||
*
|
||||
* 由于可以多次退款,记录最后一次退款的时间
|
||||
* 退款完成时间
|
||||
*/
|
||||
private Date refundTime;
|
||||
private LocalDateTime refundTime;
|
||||
|
||||
}
|
||||
|
|
|
@ -80,7 +80,6 @@ public class PayRefundDO extends BaseDO {
|
|||
*/
|
||||
private String tradeNo;
|
||||
|
||||
|
||||
// ========== 商户相关字段 ==========
|
||||
/**
|
||||
* 商户订单编号
|
||||
|
@ -171,14 +170,12 @@ public class PayRefundDO extends BaseDO {
|
|||
*/
|
||||
private String channelErrorMsg;
|
||||
|
||||
|
||||
/**
|
||||
* 支付渠道的额外参数
|
||||
* 参见 https://www.pingxx.com/api/Refunds%20退款概述.html
|
||||
*/
|
||||
private String channelExtras;
|
||||
|
||||
|
||||
/**
|
||||
* TODO
|
||||
* 退款失效时间
|
||||
|
@ -193,5 +190,4 @@ public class PayRefundDO extends BaseDO {
|
|||
*/
|
||||
private LocalDateTime notifyTime;
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -20,4 +20,9 @@ public interface PayDemoOrderMapper extends BaseMapperX<PayDemoOrderDO> {
|
|||
.orderByDesc(PayDemoOrderDO::getId));
|
||||
}
|
||||
|
||||
default int updateByIdAndPayed(Long id, boolean wherePayed, PayDemoOrderDO updateObj) {
|
||||
return update(updateObj, new LambdaQueryWrapperX<PayDemoOrderDO>()
|
||||
.eq(PayDemoOrderDO::getId, id).eq(PayDemoOrderDO::getPayed, wherePayed));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -5,5 +5,5 @@ import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
|
|||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
@Mapper
|
||||
public interface PayNotifyLogCoreMapper extends BaseMapperX<PayNotifyLogDO> {
|
||||
public interface PayNotifyLogMapper extends BaseMapperX<PayNotifyLogDO> {
|
||||
}
|
|
@ -10,7 +10,7 @@ import java.time.LocalDateTime;
|
|||
import java.util.List;
|
||||
|
||||
@Mapper
|
||||
public interface PayNotifyTaskCoreMapper extends BaseMapperX<PayNotifyTaskDO> {
|
||||
public interface PayNotifyTaskMapper extends BaseMapperX<PayNotifyTaskDO> {
|
||||
|
||||
/**
|
||||
* 获得需要通知的 PayNotifyTaskDO 记录。需要满足如下条件:
|
|
@ -39,4 +39,28 @@ public interface PayDemoOrderService {
|
|||
*/
|
||||
PageResult<PayDemoOrderDO> 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 payRefundId 退款订单号
|
||||
*/
|
||||
void updateDemoOrderRefunded(Long id, Long payRefundId);
|
||||
|
||||
}
|
||||
|
|
|
@ -1,23 +1,38 @@
|
|||
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.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.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 lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.time.Duration;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
import static cn.hutool.core.util.ObjectUtil.*;
|
||||
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 实现类
|
||||
|
@ -26,6 +41,7 @@ import static cn.iocoder.yudao.framework.common.util.servlet.ServletUtils.getCli
|
|||
*/
|
||||
@Service
|
||||
@Validated
|
||||
@Slf4j
|
||||
public class PayDemoOrderServiceImpl implements PayDemoOrderService {
|
||||
|
||||
/**
|
||||
|
@ -45,6 +61,8 @@ public class PayDemoOrderServiceImpl implements PayDemoOrderService {
|
|||
|
||||
@Resource
|
||||
private PayOrderApi payOrderApi;
|
||||
@Resource
|
||||
private PayRefundApi payRefundApi;
|
||||
|
||||
@Resource
|
||||
private PayDemoOrderMapper payDemoOrderMapper;
|
||||
|
@ -53,8 +71,8 @@ public class PayDemoOrderServiceImpl implements PayDemoOrderService {
|
|||
spuNames.put(1L, new Object[]{"华为手机", 1});
|
||||
spuNames.put(2L, new Object[]{"小米电视", 10});
|
||||
spuNames.put(3L, new Object[]{"苹果手表", 100});
|
||||
spuNames.put(4L, new Object[]{"华硕笔记本", 200});
|
||||
spuNames.put(5L, new Object[]{"蔚来汽车", 300});
|
||||
spuNames.put(4L, new Object[]{"华硕笔记本", 1000});
|
||||
spuNames.put(5L, new Object[]{"蔚来汽车", 200000});
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -67,7 +85,7 @@ public class PayDemoOrderServiceImpl implements PayDemoOrderService {
|
|||
// 1.2 插入 demo 订单
|
||||
PayDemoOrderDO demoOrder = new PayDemoOrderDO().setUserId(userId)
|
||||
.setSpuId(createReqVO.getSpuId()).setSpuName(spuName)
|
||||
.setPayed(false).setRefundPrice(0);
|
||||
.setPrice(price).setPayed(false).setRefundPrice(0);
|
||||
payDemoOrderMapper.insert(demoOrder);
|
||||
|
||||
// 2.1 创建支付单
|
||||
|
@ -99,4 +117,152 @@ public class PayDemoOrderServiceImpl implements PayDemoOrderService {
|
|||
return payDemoOrderMapper.selectPage(pageReqVO);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateDemoOrderPaid(Long id, Long payOrderId) {
|
||||
// 校验并获得支付订单(可支付)
|
||||
PayOrderRespDTO payOrder = validateDemoOrderCanPaid(id, payOrderId);
|
||||
|
||||
// 更新 PayDemoOrderDO 状态为已支付
|
||||
int updateCount = payDemoOrderMapper.updateByIdAndPayed(id, false,
|
||||
new PayDemoOrderDO().setPayed(true).setPayTime(LocalDateTime.now())
|
||||
.setPayChannelCode(payOrder.getChannelCode()));
|
||||
if (updateCount == 0) {
|
||||
throw exception(PAY_DEMO_ORDER_UPDATE_PAID_STATUS_NOT_UNPAID);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验交易订单满足被支付的条件
|
||||
*
|
||||
* 1. 交易订单未支付
|
||||
* 2. 支付单已支付
|
||||
*
|
||||
* @param id 交易订单编号
|
||||
* @param payOrderId 支付订单编号
|
||||
* @return 交易订单
|
||||
*/
|
||||
private PayOrderRespDTO validateDemoOrderCanPaid(Long id, Long payOrderId) {
|
||||
// 1.1 校验订单是否存在
|
||||
PayDemoOrderDO order = payDemoOrderMapper.selectById(id);
|
||||
if (order == null) {
|
||||
throw exception(PAY_DEMO_ORDER_NOT_FOUND);
|
||||
}
|
||||
// 1.2 校验订单未支付
|
||||
if (order.getPayed()) {
|
||||
log.error("[validateDemoOrderCanPaid][order({}) 不处于待支付状态,请进行处理!order 数据是:{}]",
|
||||
id, toJsonString(order));
|
||||
throw exception(PAY_DEMO_ORDER_UPDATE_PAID_STATUS_NOT_UNPAID);
|
||||
}
|
||||
// 1.3 校验支付订单匹配
|
||||
if (notEqual(order.getPayOrderId(), payOrderId)) { // 支付单号
|
||||
log.error("[validateDemoOrderCanPaid][order({}) 支付单不匹配({}),请进行处理!order 数据是:{}]",
|
||||
id, payOrderId, toJsonString(order));
|
||||
throw exception(PAY_DEMO_ORDER_UPDATE_PAID_FAIL_PAY_ORDER_ID_ERROR);
|
||||
}
|
||||
|
||||
// 2.1 校验支付单是否存在
|
||||
PayOrderRespDTO payOrder = payOrderApi.getOrder(payOrderId);
|
||||
if (payOrder == null) {
|
||||
log.error("[validateDemoOrderCanPaid][order({}) payOrder({}) 不存在,请进行处理!]", id, payOrderId);
|
||||
throw exception(PAY_ORDER_NOT_FOUND);
|
||||
}
|
||||
// 2.2 校验支付单已支付
|
||||
if (!PayOrderStatusEnum.isSuccess(payOrder.getStatus())) {
|
||||
log.error("[validateDemoOrderCanPaid][order({}) payOrder({}) 未支付,请进行处理!payOrder 数据是:{}]",
|
||||
id, payOrderId, toJsonString(payOrder));
|
||||
throw exception(PAY_DEMO_ORDER_UPDATE_PAID_FAIL_PAY_ORDER_STATUS_NOT_SUCCESS);
|
||||
}
|
||||
// 2.3 校验支付金额一致
|
||||
if (notEqual(payOrder.getAmount(), order.getPrice())) {
|
||||
log.error("[validateDemoOrderCanPaid][order({}) payOrder({}) 支付金额不匹配,请进行处理!order 数据是:{},payOrder 数据是:{}]",
|
||||
id, payOrderId, toJsonString(order), toJsonString(payOrder));
|
||||
throw exception(PAY_DEMO_ORDER_UPDATE_PAID_FAIL_PAY_PRICE_NOT_MATCH);
|
||||
}
|
||||
// 2.4 校验支付订单匹配(二次)
|
||||
if (notEqual(payOrder.getMerchantOrderId(), id.toString())) {
|
||||
log.error("[validateDemoOrderCanPaid][order({}) 支付单不匹配({}),请进行处理!payOrder 数据是:{}]",
|
||||
id, payOrderId, toJsonString(payOrder));
|
||||
throw exception(PAY_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 创建退款单
|
||||
Long payRefundId = payRefundApi.createPayRefund(new PayRefundCreateReqDTO()
|
||||
.setAppId(PAY_APP_ID).setUserIp(getClientIP()) // 支付应用
|
||||
.setPayOrderId(order.getPayOrderId()) // 支付单号
|
||||
.setReason("想退钱").setAmount(order.getPrice()));// 价格信息
|
||||
// 2.2 更新退款单到 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(PAY_DEMO_ORDER_NOT_FOUND);
|
||||
}
|
||||
// 校验订单是否支付
|
||||
if (!order.getPayed()) {
|
||||
throw exception(PAY_DEMO_ORDER_REFUND_FAIL_NOT_PAID);
|
||||
}
|
||||
// 校验订单是否已退款
|
||||
if (order.getPayRefundId() != null) {
|
||||
throw exception(PAY_DEMO_ORDER_REFUND_FAIL_REFUNDED);
|
||||
}
|
||||
return order;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateDemoOrderRefunded(Long id, Long payRefundId) {
|
||||
// 1. 校验并获得退款订单(可退款)
|
||||
PayRefundRespDTO payRefund = validateDemoOrderCanRefunded(id, payRefundId);
|
||||
// 2.2 更新退款单到 demo 订单
|
||||
payDemoOrderMapper.updateById(new PayDemoOrderDO().setId(id)
|
||||
.setRefundTime(payRefund.getSuccessTime()));
|
||||
}
|
||||
|
||||
private PayRefundRespDTO validateDemoOrderCanRefunded(Long id, Long payRefundId) {
|
||||
// 1.1 校验示例订单
|
||||
PayDemoOrderDO order = payDemoOrderMapper.selectById(id);
|
||||
if (order == null) {
|
||||
throw exception(PAY_DEMO_ORDER_NOT_FOUND);
|
||||
}
|
||||
// 1.2 校验退款订单匹配
|
||||
if (Objects.equals(order.getPayOrderId(), payRefundId)) {
|
||||
log.error("[validateDemoOrderCanRefunded][order({}) 退款单不匹配({}),请进行处理!order 数据是:{}]",
|
||||
id, payRefundId, toJsonString(order));
|
||||
throw exception(PAY_DEMO_ORDER_REFUND_FAIL_REFUND_ORDER_ID_ERROR);
|
||||
}
|
||||
|
||||
// 2.1 校验退款订单
|
||||
PayRefundRespDTO payRefund = payRefundApi.getPayRefund(payRefundId);
|
||||
if (payRefund == null) {
|
||||
throw exception(PAY_DEMO_ORDER_REFUND_FAIL_REFUND_NOT_FOUND);
|
||||
}
|
||||
// 2.2
|
||||
if (!PayRefundStatusEnum.isSuccess(payRefund.getStatus())) {
|
||||
throw exception(PAY_DEMO_ORDER_REFUND_FAIL_REFUND_NOT_SUCCESS);
|
||||
}
|
||||
// 2.3 校验退款金额一致
|
||||
if (notEqual(payRefund.getRefundAmount(), order.getPrice())) {
|
||||
log.error("[validateDemoOrderCanRefunded][order({}) payRefund({}) 退款金额不匹配,请进行处理!order 数据是:{},payRefund 数据是:{}]",
|
||||
id, payRefundId, toJsonString(order), toJsonString(payRefund));
|
||||
throw exception(PAY_DEMO_ORDER_REFUND_FAIL_REFUND_PRICE_NOT_MATCH);
|
||||
}
|
||||
// 2.4 校验退款订单匹配(二次)
|
||||
if (notEqual(payRefund.getMerchantOrderId(), id.toString())) {
|
||||
log.error("[validateDemoOrderCanRefunded][order({}) 退款单不匹配({}),请进行处理!payRefund 数据是:{}]",
|
||||
id, payRefundId, toJsonString(payRefund));
|
||||
throw exception(PAY_DEMO_ORDER_REFUND_FAIL_REFUND_ORDER_ID_ERROR);
|
||||
}
|
||||
return payRefund;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -13,8 +13,8 @@ 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.PayNotifyLogCoreMapper;
|
||||
import cn.iocoder.yudao.module.pay.dal.mysql.notify.PayNotifyTaskCoreMapper;
|
||||
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;
|
||||
|
@ -32,6 +32,7 @@ import org.springframework.transaction.annotation.Transactional;
|
|||
|
||||
import javax.annotation.Resource;
|
||||
import javax.validation.Valid;
|
||||
import java.time.Duration;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
|
@ -40,6 +41,7 @@ import java.util.Objects;
|
|||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.*;
|
||||
import static cn.iocoder.yudao.module.pay.framework.job.config.PayJobConfiguration.NOTIFY_THREAD_POOL_TASK_EXECUTOR;
|
||||
|
||||
/**
|
||||
|
@ -69,9 +71,9 @@ public class PayNotifyServiceImpl implements PayNotifyService {
|
|||
private PayRefundService refundService;
|
||||
|
||||
@Resource
|
||||
private PayNotifyTaskCoreMapper payNotifyTaskCoreMapper;
|
||||
private PayNotifyTaskMapper payNotifyTaskMapper;
|
||||
@Resource
|
||||
private PayNotifyLogCoreMapper payNotifyLogCoreMapper;
|
||||
private PayNotifyLogMapper payNotifyLogMapper;
|
||||
|
||||
@Resource(name = NOTIFY_THREAD_POOL_TASK_EXECUTOR)
|
||||
private ThreadPoolTaskExecutor threadPoolTaskExecutor;
|
||||
|
@ -101,7 +103,7 @@ public class PayNotifyServiceImpl implements PayNotifyService {
|
|||
}
|
||||
|
||||
// 执行插入
|
||||
payNotifyTaskCoreMapper.insert(task);
|
||||
payNotifyTaskMapper.insert(task);
|
||||
|
||||
// 异步直接发起任务。虽然会有定时任务扫描,但是会导致延迟
|
||||
self.executeNotifyAsync(task);
|
||||
|
@ -110,7 +112,7 @@ public class PayNotifyServiceImpl implements PayNotifyService {
|
|||
@Override
|
||||
public int executeNotify() throws InterruptedException {
|
||||
// 获得需要通知的任务
|
||||
List<PayNotifyTaskDO> tasks = payNotifyTaskCoreMapper.selectListByNotify();
|
||||
List<PayNotifyTaskDO> tasks = payNotifyTaskMapper.selectListByNotify();
|
||||
if (CollUtil.isEmpty(tasks)) {
|
||||
return 0;
|
||||
}
|
||||
|
@ -168,8 +170,8 @@ public class PayNotifyServiceImpl implements PayNotifyService {
|
|||
payNotifyLockCoreRedisDAO.lock(task.getId(), NOTIFY_TIMEOUT_MILLIS, () -> {
|
||||
// 校验,当前任务是否已经被通知过
|
||||
// 虽然已经通过分布式加锁,但是可能同时满足通知的条件,然后都去获得锁。此时,第一个执行完后,第二个还是能拿到锁,然后会再执行一次。
|
||||
PayNotifyTaskDO dbTask = payNotifyTaskCoreMapper.selectById(task.getId());
|
||||
if (LocalDateTimeUtils.afterNow(dbTask.getNextNotifyTime())) {
|
||||
PayNotifyTaskDO dbTask = payNotifyTaskMapper.selectById(task.getId());
|
||||
if (afterNow(dbTask.getNextNotifyTime())) {
|
||||
log.info("[executeNotifySync][dbTask({}) 任务被忽略,原因是未到达下次通知时间,可能是因为并发执行了]",
|
||||
JsonUtils.toJsonString(dbTask));
|
||||
return;
|
||||
|
@ -197,7 +199,7 @@ public class PayNotifyServiceImpl implements PayNotifyService {
|
|||
// 记录 PayNotifyLog 日志
|
||||
String response = invokeException != null ? ExceptionUtil.getRootCauseMessage(invokeException) :
|
||||
JsonUtils.toJsonString(invokeResult);
|
||||
payNotifyLogCoreMapper.insert(PayNotifyLogDO.builder().taskId(task.getId())
|
||||
payNotifyLogMapper.insert(PayNotifyLogDO.builder().taskId(task.getId())
|
||||
.notifyTimes(task.getNotifyTimes() + 1).status(newStatus).response(response).build());
|
||||
}
|
||||
|
||||
|
@ -250,23 +252,22 @@ public class PayNotifyServiceImpl implements PayNotifyService {
|
|||
// 情况一:调用成功
|
||||
if (invokeResult != null && invokeResult.isSuccess()) {
|
||||
updateTask.setStatus(PayNotifyStatusEnum.SUCCESS.getStatus());
|
||||
payNotifyTaskMapper.updateById(updateTask);
|
||||
return updateTask.getStatus();
|
||||
}
|
||||
// 情况二:调用失败、调用异常
|
||||
// 2.1 超过最大回调次数
|
||||
if (updateTask.getNotifyTimes() >= PayNotifyTaskDO.NOTIFY_FREQUENCY.length) {
|
||||
updateTask.setStatus(PayNotifyStatusEnum.FAILURE.getStatus());
|
||||
payNotifyTaskMapper.updateById(updateTask);
|
||||
return updateTask.getStatus();
|
||||
}
|
||||
// 2.2 未超过最大回调次数
|
||||
updateTask.setNextNotifyTime(LocalDateTime.now().plusSeconds(PayNotifyTaskDO.NOTIFY_FREQUENCY[updateTask.getNotifyTimes()]));
|
||||
updateTask.setNextNotifyTime(addTime(Duration.ofSeconds(PayNotifyTaskDO.NOTIFY_FREQUENCY[updateTask.getNotifyTimes()])));
|
||||
updateTask.setStatus(invokeException != null ? PayNotifyStatusEnum.REQUEST_FAILURE.getStatus()
|
||||
: PayNotifyStatusEnum.REQUEST_SUCCESS.getStatus());
|
||||
payNotifyTaskMapper.updateById(updateTask);
|
||||
return updateTask.getStatus();
|
||||
}
|
||||
|
||||
private void processNotifySuccess(PayNotifyTaskDO task, PayNotifyTaskDO updateTask) {
|
||||
payNotifyTaskCoreMapper.updateById(updateTask);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,16 +1,18 @@
|
|||
package cn.iocoder.yudao.module.pay.service.order;
|
||||
|
||||
import cn.iocoder.yudao.framework.pay.core.client.dto.PayNotifyDataDTO;
|
||||
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.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
|
||||
import cn.iocoder.yudao.module.pay.dal.dataobject.order.PayOrderDO;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.dto.notify.PayNotifyReqDTO;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.dto.notify.PayOrderNotifyRespDTO;
|
||||
import cn.iocoder.yudao.module.pay.api.order.dto.PayOrderCreateReqDTO;
|
||||
import cn.iocoder.yudao.module.pay.service.order.dto.PayOrderSubmitReqDTO;
|
||||
import cn.iocoder.yudao.module.pay.service.order.dto.PayOrderSubmitRespDTO;
|
||||
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 javax.validation.Valid;
|
||||
import javax.validation.constraints.NotEmpty;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
@ -81,17 +83,20 @@ public interface PayOrderService {
|
|||
* 提交支付
|
||||
* 此时,会发起支付渠道的调用
|
||||
*
|
||||
* @param reqDTO 提交请求
|
||||
* @param reqVO 提交请求
|
||||
* @param userIp 提交 IP
|
||||
* @return 提交结果
|
||||
*/
|
||||
PayOrderSubmitRespDTO submitPayOrder(@Valid PayOrderSubmitReqDTO reqDTO);
|
||||
PayOrderSubmitRespVO submitPayOrder(@Valid PayOrderSubmitReqVO reqVO,
|
||||
@NotEmpty(message = "提交 IP 不能为空") String userIp);
|
||||
|
||||
/**
|
||||
* 通知支付单成功
|
||||
*
|
||||
* @param channelId 渠道编号
|
||||
* @param notifyData 通知数据
|
||||
* @param notify 通知
|
||||
* @param rawNotify 通知数据
|
||||
*/
|
||||
void notifyPayOrder(Long channelId, PayNotifyDataDTO notifyData) throws Exception;
|
||||
void notifyPayOrder(Long channelId, PayOrderNotifyRespDTO notify, PayNotifyReqDTO rawNotify);
|
||||
|
||||
}
|
||||
|
|
|
@ -3,19 +3,21 @@ package cn.iocoder.yudao.module.pay.service.order;
|
|||
import cn.hutool.core.date.DateUtil;
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import cn.hutool.core.util.RandomUtil;
|
||||
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
|
||||
import cn.iocoder.yudao.framework.pay.config.PayProperties;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.PayClient;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.PayClientFactory;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.dto.PayNotifyDataDTO;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.dto.PayOrderNotifyRespDTO;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.dto.PayOrderUnifiedReqDTO;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.dto.notify.PayNotifyReqDTO;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.dto.notify.PayOrderNotifyRespDTO;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderUnifiedReqDTO;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderUnifiedRespDTO;
|
||||
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.merchant.PayAppDO;
|
||||
import cn.iocoder.yudao.module.pay.dal.dataobject.merchant.PayChannelDO;
|
||||
|
@ -31,8 +33,6 @@ import cn.iocoder.yudao.module.pay.service.merchant.PayAppService;
|
|||
import cn.iocoder.yudao.module.pay.service.merchant.PayChannelService;
|
||||
import cn.iocoder.yudao.module.pay.service.notify.PayNotifyService;
|
||||
import cn.iocoder.yudao.module.pay.service.notify.dto.PayNotifyTaskCreateReqDTO;
|
||||
import cn.iocoder.yudao.module.pay.service.order.dto.PayOrderSubmitReqDTO;
|
||||
import cn.iocoder.yudao.module.pay.service.order.dto.PayOrderSubmitRespDTO;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
@ -42,9 +42,9 @@ import javax.annotation.Resource;
|
|||
import java.time.LocalDateTime;
|
||||
import java.util.Collection;
|
||||
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.*;
|
||||
|
||||
/**
|
||||
* 支付订单 Service 实现类
|
||||
|
@ -105,7 +105,7 @@ public class PayOrderServiceImpl implements PayOrderService {
|
|||
reqDTO.getAppId(), reqDTO.getMerchantOrderId());
|
||||
if (order != null) {
|
||||
log.warn("[createPayOrder][appId({}) merchantOrderId({}) 已经存在对应的支付单({})]", order.getAppId(),
|
||||
order.getMerchantOrderId(), JsonUtils.toJsonString(order)); // 理论来说,不会出现这个情况
|
||||
order.getMerchantOrderId(), toJsonString(order)); // 理论来说,不会出现这个情况
|
||||
return order.getId();
|
||||
}
|
||||
|
||||
|
@ -127,51 +127,60 @@ public class PayOrderServiceImpl implements PayOrderService {
|
|||
}
|
||||
|
||||
@Override
|
||||
public PayOrderSubmitRespDTO submitPayOrder(PayOrderSubmitReqDTO reqDTO) {
|
||||
// 校验 App
|
||||
appService.validPayApp(reqDTO.getAppId());
|
||||
// 校验支付渠道是否有效
|
||||
PayChannelDO channel = channelService.validPayChannel(reqDTO.getAppId(), reqDTO.getChannelCode());
|
||||
// 校验支付客户端是否正确初始化
|
||||
public PayOrderSubmitRespVO submitPayOrder(PayOrderSubmitReqVO reqVO, String userIp) {
|
||||
// 1. 获得 PayOrderDO ,并校验其是否存在
|
||||
PayOrderDO order = validatePayOrderCanSubmit(reqVO.getId());
|
||||
// 1.2 校验支付渠道是否有效
|
||||
PayChannelDO channel = validatePayChannelCanSubmit(order.getAppId(), reqVO.getChannelCode());
|
||||
PayClient client = payClientFactory.getPayClient(channel.getId());
|
||||
if (client == null) {
|
||||
log.error("[submitPayOrder][渠道编号({}) 找不到对应的支付客户端]", channel.getId());
|
||||
throw exception(ErrorCodeConstants.PAY_CHANNEL_CLIENT_NOT_FOUND);
|
||||
}
|
||||
|
||||
// 获得 PayOrderDO ,并校验其是否存在
|
||||
PayOrderDO order = orderMapper.selectById(reqDTO.getId());
|
||||
if (order == null || !Objects.equals(order.getAppId(), reqDTO.getAppId())) { // 是否存在
|
||||
throw exception(ErrorCodeConstants.PAY_ORDER_NOT_FOUND);
|
||||
}
|
||||
if (!PayOrderStatusEnum.WAITING.getStatus().equals(order.getStatus())) { // 校验状态,必须是待支付
|
||||
throw exception(ErrorCodeConstants.PAY_ORDER_STATUS_IS_NOT_WAITING);
|
||||
}
|
||||
|
||||
// 插入 PayOrderExtensionDO
|
||||
PayOrderExtensionDO orderExtension = PayOrderConvert.INSTANCE.convert(reqDTO)
|
||||
// 2. 插入 PayOrderExtensionDO
|
||||
PayOrderExtensionDO orderExtension = PayOrderConvert.INSTANCE.convert(reqVO, userIp)
|
||||
.setOrderId(order.getId()).setNo(generateOrderExtensionNo())
|
||||
.setChannelId(channel.getId()).setChannelCode(channel.getCode())
|
||||
.setStatus(PayOrderStatusEnum.WAITING.getStatus());
|
||||
orderExtensionMapper.insert(orderExtension);
|
||||
|
||||
// 调用三方接口
|
||||
PayOrderUnifiedReqDTO unifiedOrderReqDTO = PayOrderConvert.INSTANCE.convert2(reqDTO);
|
||||
// 商户相关字段
|
||||
//TODO jason @芋艿 是否加一个属性 如tradeNo 支付订单号, 用这个merchantOrderId让人迷糊
|
||||
unifiedOrderReqDTO.setMerchantOrderId(orderExtension.getNo()) // 注意,此处使用的是 PayOrderExtensionDO.no 属性!
|
||||
// 3. 调用三方接口
|
||||
PayOrderUnifiedReqDTO unifiedOrderReqDTO = PayOrderConvert.INSTANCE.convert2(reqVO)
|
||||
// 商户相关的字段
|
||||
.setMerchantOrderId(orderExtension.getNo()) // 注意,此处使用的是 PayOrderExtensionDO.no 属性!
|
||||
.setSubject(order.getSubject()).setBody(order.getBody())
|
||||
.setNotifyUrl(genChannelPayNotifyUrl(channel))
|
||||
.setReturnUrl(genChannelReturnUrl(channel));
|
||||
.setReturnUrl(genChannelReturnUrl(channel))
|
||||
// 订单相关字段
|
||||
unifiedOrderReqDTO.setAmount(order.getAmount()).setExpireTime(order.getExpireTime());
|
||||
CommonResult<?> unifiedOrderResult = client.unifiedOrder(unifiedOrderReqDTO);
|
||||
unifiedOrderResult.checkError();
|
||||
.setAmount(order.getAmount()).setExpireTime(order.getExpireTime());
|
||||
PayOrderUnifiedRespDTO unifiedOrderRespDTO = client.unifiedOrder(unifiedOrderReqDTO);
|
||||
|
||||
// TODO 轮询三方接口,是否已经支付的任务
|
||||
// 返回成功
|
||||
return new PayOrderSubmitRespDTO().setExtensionId(orderExtension.getId())
|
||||
.setInvokeResponse(unifiedOrderResult.getData());
|
||||
return PayOrderConvert.INSTANCE.convert(unifiedOrderRespDTO);
|
||||
}
|
||||
|
||||
private PayOrderDO validatePayOrderCanSubmit(Long id) {
|
||||
PayOrderDO order = orderMapper.selectById(id);
|
||||
if (order == null) { // 是否存在
|
||||
throw exception(ErrorCodeConstants.PAY_ORDER_NOT_FOUND);
|
||||
}
|
||||
if (!PayOrderStatusEnum.WAITING.getStatus().equals(order.getStatus())) { // 校验状态,必须是待支付
|
||||
throw exception(ErrorCodeConstants.PAY_ORDER_STATUS_IS_NOT_WAITING);
|
||||
}
|
||||
return order;
|
||||
}
|
||||
|
||||
private PayChannelDO validatePayChannelCanSubmit(Long appId, String channelCode) {
|
||||
// 校验 App
|
||||
appService.validPayApp(appId);
|
||||
|
||||
// 校验支付渠道是否有效
|
||||
PayChannelDO channel = channelService.validPayChannel(appId, channelCode);
|
||||
// 校验支付客户端是否正确初始化
|
||||
PayClient client = payClientFactory.getPayClient(channel.getId());
|
||||
if (client == null) {
|
||||
log.error("[validatePayChannelCanSubmit][渠道编号({}) 找不到对应的支付客户端]", channel.getId());
|
||||
throw exception(ErrorCodeConstants.PAY_CHANNEL_CLIENT_NOT_FOUND);
|
||||
}
|
||||
return channel;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -213,49 +222,30 @@ public class PayOrderServiceImpl implements PayOrderService {
|
|||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void notifyPayOrder(Long channelId, PayNotifyDataDTO notifyData) {
|
||||
// TODO 芋艿,记录回调日志
|
||||
log.info("[notifyPayOrder][channelId({}) 回调数据({})]", channelId, notifyData.getBody());
|
||||
|
||||
public void notifyPayOrder(Long channelId, PayOrderNotifyRespDTO notify, PayNotifyReqDTO rawNotify) {
|
||||
// 校验支付渠道是否有效
|
||||
PayChannelDO channel = channelService.validPayChannel(channelId);
|
||||
TenantUtils.execute(channel.getTenantId(), () -> {
|
||||
try {
|
||||
notifyPayOrder(channel, notifyData);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void notifyPayOrder(PayChannelDO channel, PayNotifyDataDTO notifyData) throws Exception {
|
||||
// 校验支付客户端是否正确初始化
|
||||
PayClient client = payClientFactory.getPayClient(channel.getId());
|
||||
if (client == null) {
|
||||
log.error("[notifyPayOrder][渠道编号({}) 找不到对应的支付客户端]", channel.getId());
|
||||
throw exception(ErrorCodeConstants.PAY_CHANNEL_CLIENT_NOT_FOUND);
|
||||
}
|
||||
|
||||
// 0. 解析支付结果
|
||||
PayOrderNotifyRespDTO notifyRespDTO = client.parseOrderNotify(notifyData);
|
||||
// 1. 更新 PayOrderExtensionDO 支付成功
|
||||
PayOrderExtensionDO orderExtension = updatePayOrderExtensionSuccess(notifyRespDTO.getOrderExtensionNo(), notifyData.getBody());
|
||||
PayOrderExtensionDO orderExtension = updatePayOrderExtensionSuccess(notify.getOrderExtensionNo(),
|
||||
rawNotify);
|
||||
// 2. 更新 PayOrderDO 支付成功
|
||||
PayOrderDO order = updatePayOrderSuccess(channel, orderExtension, notifyRespDTO);
|
||||
PayOrderDO order = updatePayOrderSuccess(channel, orderExtension, notify);
|
||||
|
||||
// 3. 插入支付通知记录
|
||||
notifyService.createPayNotifyTask(PayNotifyTaskCreateReqDTO.builder()
|
||||
.type(PayNotifyTypeEnum.ORDER.getType()).dataId(order.getId()).build());
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新 PayOrderExtensionDO 支付成功
|
||||
*
|
||||
* @param no 支付订单号(支付模块)
|
||||
* @param body 回调内容
|
||||
* @param rawNotify 通知数据
|
||||
* @return PayOrderExtensionDO 对象
|
||||
*/
|
||||
private PayOrderExtensionDO updatePayOrderExtensionSuccess(String no, String body) {
|
||||
private PayOrderExtensionDO updatePayOrderExtensionSuccess(String no, PayNotifyReqDTO rawNotify) {
|
||||
// 1.1 查询 PayOrderExtensionDO
|
||||
PayOrderExtensionDO orderExtension = orderExtensionMapper.selectByNo(no);
|
||||
if (orderExtension == null) {
|
||||
|
@ -267,7 +257,8 @@ public class PayOrderServiceImpl implements PayOrderService {
|
|||
// 1.2 更新 PayOrderExtensionDO
|
||||
int updateCounts = orderExtensionMapper.updateByIdAndStatus(orderExtension.getId(),
|
||||
PayOrderStatusEnum.WAITING.getStatus(), PayOrderExtensionDO.builder().id(orderExtension.getId())
|
||||
.status(PayOrderStatusEnum.SUCCESS.getStatus()).channelNotifyData(body).build());
|
||||
.status(PayOrderStatusEnum.SUCCESS.getStatus())
|
||||
.channelNotifyData(toJsonString(rawNotify)).build());
|
||||
if (updateCounts == 0) { // 校验状态,必须是待支付
|
||||
throw exception(ErrorCodeConstants.PAY_ORDER_EXTENSION_STATUS_IS_NOT_WAITING);
|
||||
}
|
||||
|
@ -280,11 +271,11 @@ public class PayOrderServiceImpl implements PayOrderService {
|
|||
*
|
||||
* @param channel 支付渠道
|
||||
* @param orderExtension 支付拓展单
|
||||
* @param notifyRespDTO 通知回调
|
||||
* @param notify 通知回调
|
||||
* @return PayOrderDO 对象
|
||||
*/
|
||||
private PayOrderDO updatePayOrderSuccess(PayChannelDO channel, PayOrderExtensionDO orderExtension,
|
||||
PayOrderNotifyRespDTO notifyRespDTO) {
|
||||
PayOrderNotifyRespDTO notify) {
|
||||
// 2.1 判断 PayOrderDO 是否处于待支付
|
||||
PayOrderDO order = orderMapper.selectById(orderExtension.getOrderId());
|
||||
if (order == null) {
|
||||
|
@ -297,8 +288,8 @@ public class PayOrderServiceImpl implements PayOrderService {
|
|||
int updateCounts = orderMapper.updateByIdAndStatus(order.getId(), PayOrderStatusEnum.WAITING.getStatus(),
|
||||
PayOrderDO.builder().status(PayOrderStatusEnum.SUCCESS.getStatus())
|
||||
.channelId(channel.getId()).channelCode(channel.getCode())
|
||||
.successTime(notifyRespDTO.getSuccessTime()).successExtensionId(orderExtension.getId())
|
||||
.channelOrderNo(notifyRespDTO.getChannelOrderNo()).channelUserId(notifyRespDTO.getChannelUserId())
|
||||
.successTime(notify.getSuccessTime()).successExtensionId(orderExtension.getId())
|
||||
.channelOrderNo(notify.getChannelOrderNo()).channelUserId(notify.getChannelUserId())
|
||||
.notifyTime(LocalDateTime.now()).build());
|
||||
if (updateCounts == 0) { // 校验状态,必须是待支付
|
||||
throw exception(ErrorCodeConstants.PAY_ORDER_STATUS_IS_NOT_WAITING);
|
||||
|
|
|
@ -1,47 +0,0 @@
|
|||
package cn.iocoder.yudao.module.pay.service.order.dto;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
import javax.validation.constraints.NotEmpty;
|
||||
import javax.validation.constraints.NotNull;
|
||||
import java.io.Serializable;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 支付单提交 Request DTO
|
||||
*/
|
||||
@Data
|
||||
@Accessors(chain = true)
|
||||
public class PayOrderSubmitReqDTO implements Serializable {
|
||||
|
||||
/**
|
||||
* 应用编号
|
||||
*/
|
||||
@NotNull(message = "应用编号不能为空")
|
||||
private Long appId;
|
||||
|
||||
/**
|
||||
* 支付单编号
|
||||
*/
|
||||
@NotNull(message = "支付单编号不能为空")
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 支付渠道
|
||||
*/
|
||||
@NotEmpty(message = "支付渠道不能为空")
|
||||
private String channelCode;
|
||||
|
||||
/**
|
||||
* 用户 IP
|
||||
*/
|
||||
@NotEmpty(message = "用户 IP 不能为空")
|
||||
private String userIp;
|
||||
|
||||
/**
|
||||
* 支付渠道的额外参数
|
||||
*/
|
||||
private Map<String, String> channelExtras;
|
||||
|
||||
}
|
|
@ -1,23 +0,0 @@
|
|||
package cn.iocoder.yudao.module.pay.service.order.dto;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 支付单提交 Response DTO
|
||||
*/
|
||||
@Data
|
||||
public class PayOrderSubmitRespDTO implements Serializable {
|
||||
|
||||
/**
|
||||
* 支付拓展单的编号
|
||||
*/
|
||||
private Long extensionId;
|
||||
|
||||
/**
|
||||
* 调用支付渠道的响应结果
|
||||
*/
|
||||
private Object invokeResponse;
|
||||
|
||||
}
|
|
@ -1,51 +0,0 @@
|
|||
package cn.iocoder.yudao.module.pay.service.order.dto;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
import javax.validation.constraints.DecimalMin;
|
||||
import javax.validation.constraints.NotEmpty;
|
||||
import javax.validation.constraints.NotNull;
|
||||
|
||||
/**
|
||||
* 退款申请单 Request DTO
|
||||
*/
|
||||
@Data
|
||||
@Accessors(chain = true)
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class PayRefundReqDTO {
|
||||
|
||||
/**
|
||||
* 支付订单编号
|
||||
*/
|
||||
@NotNull(message = "支付订单编号不能为空")
|
||||
private Long payOrderId;
|
||||
|
||||
/**
|
||||
* 退款金额
|
||||
*/
|
||||
@NotNull(message = "退款金额不能为空")
|
||||
@DecimalMin(value = "0", inclusive = false, message = "退款金额必须大于零")
|
||||
private Integer amount;
|
||||
|
||||
/**
|
||||
* 退款原因
|
||||
*/
|
||||
private String reason;
|
||||
|
||||
/**
|
||||
* 商户退款订单号
|
||||
*/
|
||||
@NotEmpty(message = "商户退款订单号不能为空")
|
||||
private String merchantRefundId;
|
||||
|
||||
/**
|
||||
* 用户 IP
|
||||
*/
|
||||
private String userIp;
|
||||
}
|
|
@ -1,24 +0,0 @@
|
|||
package cn.iocoder.yudao.module.pay.service.order.dto;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
/**
|
||||
* 退款申请单 Response DTO
|
||||
*/
|
||||
@Data
|
||||
@Accessors(chain = true)
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class PayRefundRespDTO {
|
||||
|
||||
/**
|
||||
* 支付退款单编号,自增
|
||||
*/
|
||||
private Long refundId;
|
||||
|
||||
}
|
|
@ -1,12 +1,12 @@
|
|||
package cn.iocoder.yudao.module.pay.service.refund;
|
||||
|
||||
import cn.iocoder.yudao.framework.pay.core.client.dto.PayNotifyDataDTO;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.dto.notify.PayNotifyReqDTO;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.dto.notify.PayRefundNotifyRespDTO;
|
||||
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.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.yudao.module.pay.dal.dataobject.refund.PayRefundDO;
|
||||
import cn.iocoder.yudao.module.pay.service.order.dto.PayRefundReqDTO;
|
||||
import cn.iocoder.yudao.module.pay.service.order.dto.PayRefundRespDTO;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
|
@ -42,20 +42,20 @@ public interface PayRefundService {
|
|||
List<PayRefundDO> getRefundList(PayRefundExportReqVO exportReqVO);
|
||||
|
||||
/**
|
||||
* 提交退款申请
|
||||
* 创建退款申请
|
||||
*
|
||||
* @param reqDTO 退款申请信息
|
||||
* @return 退款申请返回信息
|
||||
* @return 退款单号
|
||||
*/
|
||||
PayRefundRespDTO submitRefundOrder(PayRefundReqDTO reqDTO);
|
||||
Long createPayRefund(PayRefundCreateReqDTO reqDTO);
|
||||
|
||||
/**
|
||||
* 渠道的退款通知
|
||||
*
|
||||
* @param channelId 渠道编号
|
||||
* @param notifyData 通知数据
|
||||
* @throws Exception 退款通知异常
|
||||
* @param notify 通知
|
||||
* @param rawNotify 通知数据
|
||||
*/
|
||||
void notifyPayRefund(Long channelId, PayNotifyDataDTO notifyData) throws Exception;
|
||||
void notifyPayRefund(Long channelId, PayRefundNotifyRespDTO notify, PayNotifyReqDTO rawNotify);
|
||||
|
||||
}
|
||||
|
|
|
@ -1,15 +1,17 @@
|
|||
package cn.iocoder.yudao.module.pay.service.refund;
|
||||
|
||||
import cn.hutool.core.util.RandomUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil;
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.yudao.framework.pay.config.PayProperties;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.PayClient;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.PayClientFactory;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.PayCommonResult;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.dto.PayNotifyDataDTO;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.dto.PayRefundNotifyDTO;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.dto.PayRefundUnifiedReqDTO;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.dto.PayRefundUnifiedRespDTO;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.dto.notify.PayNotifyReqDTO;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.dto.notify.PayRefundNotifyRespDTO;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.dto.refund.PayRefundUnifiedReqDTO;
|
||||
import cn.iocoder.yudao.framework.pay.core.enums.PayNotifyRefundStatusEnum;
|
||||
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.merchant.PayAppDO;
|
||||
|
@ -19,7 +21,6 @@ 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.mysql.order.PayOrderMapper;
|
||||
import cn.iocoder.yudao.module.pay.dal.mysql.refund.PayRefundMapper;
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.yudao.module.pay.enums.ErrorCodeConstants;
|
||||
import cn.iocoder.yudao.module.pay.enums.notify.PayNotifyTypeEnum;
|
||||
import cn.iocoder.yudao.module.pay.enums.order.PayOrderNotifyStatusEnum;
|
||||
|
@ -32,8 +33,6 @@ import cn.iocoder.yudao.module.pay.service.notify.PayNotifyService;
|
|||
import cn.iocoder.yudao.module.pay.service.notify.dto.PayNotifyTaskCreateReqDTO;
|
||||
import cn.iocoder.yudao.module.pay.service.order.PayOrderExtensionService;
|
||||
import cn.iocoder.yudao.module.pay.service.order.PayOrderService;
|
||||
import cn.iocoder.yudao.module.pay.service.order.dto.PayRefundReqDTO;
|
||||
import cn.iocoder.yudao.module.pay.service.order.dto.PayRefundRespDTO;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
@ -54,6 +53,9 @@ import java.util.Objects;
|
|||
@Validated
|
||||
public class PayRefundServiceImpl implements PayRefundService {
|
||||
|
||||
@Resource
|
||||
private PayProperties payProperties;
|
||||
|
||||
@Resource
|
||||
private PayClientFactory payClientFactory;
|
||||
|
||||
|
@ -90,9 +92,9 @@ public class PayRefundServiceImpl implements PayRefundService {
|
|||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public PayRefundRespDTO submitRefundOrder(PayRefundReqDTO req) {
|
||||
public Long createPayRefund(PayRefundCreateReqDTO reqDTO) {
|
||||
// 获得 PayOrderDO
|
||||
PayOrderDO order = orderService.getOrder(req.getPayOrderId());
|
||||
PayOrderDO order = orderService.getOrder(reqDTO.getPayOrderId());
|
||||
// 校验订单是否存在
|
||||
if (Objects.isNull(order) ) {
|
||||
throw ServiceExceptionUtil.exception(ErrorCodeConstants.PAY_ORDER_NOT_FOUND);
|
||||
|
@ -108,15 +110,19 @@ public class PayRefundServiceImpl implements PayRefundService {
|
|||
throw ServiceExceptionUtil.exception(ErrorCodeConstants.PAY_CHANNEL_CLIENT_NOT_FOUND);
|
||||
}
|
||||
|
||||
// TODO 芋艿:待实现
|
||||
String merchantRefundId = RandomUtil.randomNumbers(16);
|
||||
|
||||
// 校验退款的条件
|
||||
validatePayRefund(req, order);
|
||||
validatePayRefund(reqDTO, order);
|
||||
// 退款类型
|
||||
PayRefundTypeEnum refundType = PayRefundTypeEnum.SOME;
|
||||
if (Objects.equals(req.getAmount(), order.getAmount())) {
|
||||
if (Objects.equals(reqDTO.getAmount(), order.getAmount())) {
|
||||
refundType = PayRefundTypeEnum.ALL;
|
||||
}
|
||||
PayOrderExtensionDO orderExtensionDO = orderExtensionService.getOrderExtension(order.getSuccessExtensionId());
|
||||
PayRefundDO payRefundDO = refundMapper.selectByTradeNoAndMerchantRefundNo(orderExtensionDO.getNo(), req.getMerchantRefundId());
|
||||
PayRefundDO payRefundDO = refundMapper.selectByTradeNoAndMerchantRefundNo(orderExtensionDO.getNo(),
|
||||
merchantRefundId); // TODO 芋艿:需要优化
|
||||
if(Objects.nonNull(payRefundDO)){
|
||||
// 退款订单已经提交过。
|
||||
//TODO 校验相同退款单的金额
|
||||
|
@ -137,15 +143,15 @@ public class PayRefundServiceImpl implements PayRefundService {
|
|||
.channelId(order.getChannelId())
|
||||
.merchantId(order.getMerchantId())
|
||||
.orderId(order.getId())
|
||||
.merchantRefundNo(req.getMerchantRefundId())
|
||||
.merchantRefundNo(merchantRefundId) // TODO 芋艿:需要优化
|
||||
.notifyUrl(app.getRefundNotifyUrl())
|
||||
.payAmount(order.getAmount())
|
||||
.refundAmount(req.getAmount())
|
||||
.userIp(req.getUserIp())
|
||||
.refundAmount(reqDTO.getAmount())
|
||||
.userIp(reqDTO.getUserIp())
|
||||
.merchantOrderId(order.getMerchantOrderId())
|
||||
.tradeNo(orderExtensionDO.getNo())
|
||||
.status(PayRefundStatusEnum.CREATE.getStatus())
|
||||
.reason(req.getReason())
|
||||
.reason(reqDTO.getReason())
|
||||
.notifyStatus(PayOrderNotifyStatusEnum.NO.getStatus())
|
||||
.type(refundType.getStatus())
|
||||
.build();
|
||||
|
@ -153,47 +159,50 @@ public class PayRefundServiceImpl implements PayRefundService {
|
|||
}
|
||||
// TODO @jason:搞到 convert 里。一些额外的自动,手动 set 下;
|
||||
PayRefundUnifiedReqDTO unifiedReqDTO = new PayRefundUnifiedReqDTO();
|
||||
unifiedReqDTO.setUserIp(req.getUserIp())
|
||||
.setAmount(req.getAmount())
|
||||
unifiedReqDTO.setUserIp(reqDTO.getUserIp())
|
||||
.setAmount(reqDTO.getAmount())
|
||||
.setChannelOrderNo(order.getChannelOrderNo())
|
||||
.setPayTradeNo(orderExtensionDO.getNo())
|
||||
.setMerchantRefundId(req.getMerchantRefundId())
|
||||
.setReason(req.getReason());
|
||||
.setMerchantRefundId(merchantRefundId) // TODO 芋艿:需要优化
|
||||
.setNotifyUrl(genChannelPayNotifyUrl(channel)) // TODO 芋艿:优化下 notifyUrl
|
||||
.setReason(reqDTO.getReason());
|
||||
// 向渠道发起退款申请
|
||||
PayCommonResult<PayRefundUnifiedRespDTO> refundUnifiedResult = client.unifiedRefund(unifiedReqDTO);
|
||||
client.unifiedRefund(unifiedReqDTO);
|
||||
// 检查是否失败,失败抛出业务异常。
|
||||
// TODO 渠道的异常记录。
|
||||
// TODO @jason:可以先打个 warn log 哈;
|
||||
refundUnifiedResult.checkError();
|
||||
// 成功在 退款回调中处理
|
||||
return PayRefundRespDTO.builder().refundId(payRefundDO.getId()).build();
|
||||
return payRefundDO.getId();
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据支付渠道的编码,生成支付渠道的回调地址
|
||||
*
|
||||
* @param channel 支付渠道
|
||||
* @return 支付渠道的回调地址 配置地址 + "/" + channel id
|
||||
*/
|
||||
private String genChannelPayNotifyUrl(PayChannelDO channel) {
|
||||
return payProperties.getCallbackUrl() + "/" + channel.getId();
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void notifyPayRefund(Long channelId, PayNotifyDataDTO notifyData) {
|
||||
log.info("[notifyPayRefund][channelId({}) 回调数据({})]", channelId, notifyData.getBody());
|
||||
public void notifyPayRefund(Long channelId, PayRefundNotifyRespDTO notify, PayNotifyReqDTO rawNotify) {
|
||||
// 校验支付渠道是否有效
|
||||
// TODO 芋艿:需要重构下这块的逻辑
|
||||
PayChannelDO channel = channelService.validPayChannel(channelId);
|
||||
// 校验支付客户端是否正确初始化
|
||||
PayClient client = payClientFactory.getPayClient(channel.getId());
|
||||
if (client == null) {
|
||||
log.error("[notifyPayOrder][渠道编号({}) 找不到对应的支付客户端]", channel.getId());
|
||||
throw ServiceExceptionUtil.exception(ErrorCodeConstants.PAY_CHANNEL_CLIENT_NOT_FOUND);
|
||||
}
|
||||
// 解析渠道退款通知数据, 统一处理
|
||||
PayRefundNotifyDTO refundNotify = client.parseRefundNotify(notifyData);
|
||||
if (Objects.equals(PayNotifyRefundStatusEnum.SUCCESS,refundNotify.getStatus())){
|
||||
payRefundSuccess(refundNotify);
|
||||
if (Objects.equals(PayNotifyRefundStatusEnum.SUCCESS, notify.getStatus())){
|
||||
payRefundSuccess(notify);
|
||||
} else {
|
||||
//TODO 支付异常, 支付宝似乎没有支付异常的通知。
|
||||
// TODO @jason:那这里可以考虑打个 error logger @芋艿 微信是否存在支付异常通知
|
||||
}
|
||||
}
|
||||
|
||||
private void payRefundSuccess(PayRefundNotifyDTO refundNotify) {
|
||||
private void payRefundSuccess(PayRefundNotifyRespDTO refundNotify) {
|
||||
// 校验退款单存在
|
||||
PayRefundDO refundDO = refundMapper.selectByTradeNoAndMerchantRefundNo(refundNotify.getTradeNo(), refundNotify.getReqNo());
|
||||
PayRefundDO refundDO = refundMapper.selectByTradeNoAndMerchantRefundNo(refundNotify.getTradeNo(),
|
||||
refundNotify.getReqNo());
|
||||
if (refundDO == null) {
|
||||
log.error("[payRefundSuccess][不存在 seqNo 为{} 的支付退款单]", refundNotify.getReqNo());
|
||||
throw ServiceExceptionUtil.exception(ErrorCodeConstants.PAY_REFUND_NOT_FOUND);
|
||||
|
@ -235,10 +244,11 @@ public class PayRefundServiceImpl implements PayRefundService {
|
|||
|
||||
/**
|
||||
* 校验是否进行退款
|
||||
* @param req 退款申请信息
|
||||
*
|
||||
* @param reqDTO 退款申请信息
|
||||
* @param order 原始支付订单信息
|
||||
*/
|
||||
private void validatePayRefund(PayRefundReqDTO req, PayOrderDO order) {
|
||||
private void validatePayRefund(PayRefundCreateReqDTO reqDTO, PayOrderDO order) {
|
||||
// 校验状态,必须是支付状态
|
||||
if (!PayOrderStatusEnum.SUCCESS.getStatus().equals(order.getStatus())) {
|
||||
throw ServiceExceptionUtil.exception(ErrorCodeConstants.PAY_ORDER_STATUS_IS_NOT_SUCCESS);
|
||||
|
@ -248,7 +258,7 @@ public class PayRefundServiceImpl implements PayRefundService {
|
|||
throw ServiceExceptionUtil.exception(ErrorCodeConstants.PAY_REFUND_ALL_REFUNDED);
|
||||
}
|
||||
// 校验金额 退款金额不能大于 原定的金额
|
||||
if (req.getAmount() + order.getRefundAmount() > order.getAmount()){
|
||||
if (reqDTO.getAmount() + order.getRefundAmount() > order.getAmount()){
|
||||
throw ServiceExceptionUtil.exception(ErrorCodeConstants.PAY_REFUND_AMOUNT_EXCEED);
|
||||
}
|
||||
// 校验渠道订单号
|
||||
|
|
|
@ -18,6 +18,7 @@ public class PaySeqUtils {
|
|||
|
||||
private static final AtomicLong MER_ORDER_NO_SEQ = new AtomicLong(0L);
|
||||
|
||||
// TODO 芋艿:需要看看
|
||||
/**
|
||||
* 生成商户退款单号,用于测试,应该由商户系统生成
|
||||
* @return 商户退款单
|
||||
|
@ -28,6 +29,8 @@ public class PaySeqUtils {
|
|||
(int) MER_REFUND_NO_SEQ.getAndIncrement() % 10000);
|
||||
}
|
||||
|
||||
// TODO 芋艿:需要看看
|
||||
|
||||
/**
|
||||
* 生成退款请求号
|
||||
* @return 退款请求号
|
||||
|
|
|
@ -54,11 +54,11 @@
|
|||
<!-- <version>${revision}</version>-->
|
||||
<!-- </dependency>-->
|
||||
<!-- 支付服务 -->
|
||||
<dependency>
|
||||
<groupId>cn.iocoder.boot</groupId>
|
||||
<artifactId>yudao-module-pay-biz</artifactId>
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
<!-- <dependency>-->
|
||||
<!-- <groupId>cn.iocoder.boot</groupId>-->
|
||||
<!-- <artifactId>yudao-module-pay-biz</artifactId>-->
|
||||
<!-- <version>${revision}</version>-->
|
||||
<!-- </dependency>-->
|
||||
|
||||
<!-- 微信公众号模块。默认注释,保证编译速度 -->
|
||||
<!-- <dependency>-->
|
||||
|
|
|
@ -1,4 +0,0 @@
|
|||
/**
|
||||
* 占位
|
||||
*/
|
||||
package cn.iocoder.yudao.module.shop.controller.admin;
|
|
@ -1,73 +0,0 @@
|
|||
package cn.iocoder.yudao.module.shop.controller.app;
|
||||
|
||||
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.order.PayOrderService;
|
||||
import cn.iocoder.yudao.module.pay.api.order.dto.PayOrderCreateReqDTO;
|
||||
import cn.iocoder.yudao.module.pay.util.PaySeqUtils;
|
||||
import cn.iocoder.yudao.module.shop.controller.app.vo.AppShopOrderCreateRespVO;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import javax.validation.Valid;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
|
||||
import static cn.iocoder.yudao.framework.common.util.servlet.ServletUtils.getClientIP;
|
||||
|
||||
@Tag(name = "用户 APP - 商城订单")
|
||||
@RestController
|
||||
@RequestMapping("/shop/order")
|
||||
@Validated
|
||||
@Slf4j
|
||||
public class AppShopOrderController {
|
||||
|
||||
@Resource
|
||||
private PayOrderService payOrderService;
|
||||
|
||||
@PostMapping("/create")
|
||||
@Operation(summary = "创建商城订单")
|
||||
// @PreAuthenticated // TODO 暂时不加登陆验证,前端暂时没做好
|
||||
public CommonResult<AppShopOrderCreateRespVO> create() {
|
||||
// 假装创建商城订单
|
||||
Long shopOrderId = System.currentTimeMillis();
|
||||
|
||||
// 创建对应的支付订单
|
||||
PayOrderCreateReqDTO reqDTO = new PayOrderCreateReqDTO();
|
||||
reqDTO.setAppId(6L);
|
||||
reqDTO.setUserIp(getClientIP());
|
||||
reqDTO.setMerchantOrderId(PaySeqUtils.genMerchantOrderNo());
|
||||
reqDTO.setSubject("标题:" + shopOrderId);
|
||||
reqDTO.setBody("内容:" + shopOrderId);
|
||||
reqDTO.setAmount(200); // 单位:分
|
||||
reqDTO.setExpireTime(LocalDateTime.now().plusDays(1));
|
||||
Long payOrderId = payOrderService.createPayOrder(reqDTO);
|
||||
|
||||
// 拼接返回
|
||||
return success(AppShopOrderCreateRespVO.builder().id(shopOrderId)
|
||||
.payOrderId(payOrderId).build());
|
||||
}
|
||||
|
||||
@PostMapping("/pay-notify")
|
||||
@Operation(summary = "支付回调")
|
||||
public CommonResult<Boolean> payNotify(@RequestBody @Valid PayOrderNotifyReqDTO reqVO) {
|
||||
log.info("[payNotify][回调成功]");
|
||||
return success(true);
|
||||
}
|
||||
|
||||
@PostMapping("/refund-notify")
|
||||
@Operation(summary = "退款回调")
|
||||
public CommonResult<Boolean> refundNotify(@RequestBody @Valid PayRefundNotifyReqDTO reqVO) {
|
||||
log.info("[refundNotify][回调成功]");
|
||||
return success(true);
|
||||
}
|
||||
|
||||
}
|
|
@ -1,20 +0,0 @@
|
|||
package cn.iocoder.yudao.module.shop.controller.app.vo;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
|
||||
@Schema(description = "用户 APP - 商城订单创建 Response VO")
|
||||
@Data
|
||||
@Builder
|
||||
@AllArgsConstructor
|
||||
public class AppShopOrderCreateRespVO {
|
||||
|
||||
@Schema(description = "商城订单编号", required = true, example = "1024")
|
||||
private Long id;
|
||||
|
||||
@Schema(description = "支付订单编号", required = true, example = "2048")
|
||||
private Long payOrderId;
|
||||
|
||||
}
|
|
@ -1,9 +0,0 @@
|
|||
/**
|
||||
* shop 包下,我们放商城业务
|
||||
* 例如说:商品、订单等等
|
||||
* 注意,目前仅仅作为 demo 演示,对接 pay 支付系统
|
||||
*
|
||||
* 缩写:shop
|
||||
*/
|
||||
// TODO 芋艿:后续会迁移到 yudao-module-mall-trade 下
|
||||
package cn.iocoder.yudao.module.shop;
|
|
@ -1,11 +0,0 @@
|
|||
package cn.iocoder.yudao.server.framework.ui.core;
|
||||
|
||||
import org.springframework.boot.web.servlet.error.ErrorController;
|
||||
|
||||
//@Controller
|
||||
//@RequestMapping("/admin-ui/")
|
||||
public class AdminUiController implements ErrorController {
|
||||
|
||||
// public String
|
||||
|
||||
}
|
|
@ -153,6 +153,7 @@ logging:
|
|||
cn.iocoder.yudao.module.infra.dal.mysql: debug
|
||||
cn.iocoder.yudao.module.infra.dal.mysql.job.JobLogMapper: INFO # 配置 JobLogMapper 的日志级别为 info
|
||||
cn.iocoder.yudao.module.pay.dal.mysql: debug
|
||||
cn.iocoder.yudao.module.pay.dal.mysql.notify.PayNotifyTaskMapper: INFO # 配置 JobLogMapper 的日志级别为 info
|
||||
cn.iocoder.yudao.module.system.dal.mysql: debug
|
||||
cn.iocoder.yudao.module.tool.dal.mysql: debug
|
||||
cn.iocoder.yudao.module.member.dal.mysql: debug
|
||||
|
|
|
@ -1,79 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no"/>
|
||||
<title>支付测试页</title>
|
||||
<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.0/jquery.js"></script>
|
||||
<script src="qrcode.min.js" type="text/javascript"></script>
|
||||
|
||||
</head>
|
||||
<body>
|
||||
<div>点击如下按钮,发起支付宝扫码支付的测试</div>
|
||||
<div>
|
||||
<button id="alipay_wap">支付宝扫码支付</button>
|
||||
</div>
|
||||
<div id="qrcode"></div>
|
||||
</body>
|
||||
<style>
|
||||
#qrcode{
|
||||
padding-left: 20px;
|
||||
padding-top: 20px;
|
||||
}
|
||||
</style>
|
||||
<script>
|
||||
let shopOrderId = undefined;
|
||||
let payOrderId = undefined;
|
||||
let server = 'http://127.0.0.1:48080';
|
||||
$(function() {
|
||||
// 自动发起商城订单编号
|
||||
$.ajax({
|
||||
url: server + "/app-api/shop/order/create",
|
||||
method: 'POST',
|
||||
success: function( result ) {
|
||||
if (result.code !== 0) {
|
||||
alert('创建商城订单失败,原因:' + result.msg)
|
||||
return;
|
||||
}
|
||||
shopOrderId = result.data.id;
|
||||
payOrderId = result.data.payOrderId;
|
||||
console.log("商城订单:" + shopOrderId)
|
||||
console.log("支付订单:" + payOrderId)
|
||||
}
|
||||
})
|
||||
});
|
||||
// 支付宝扫码支付
|
||||
$( "#alipay_wap").on( "click", function() {
|
||||
// 提交支付
|
||||
$.ajax({
|
||||
url: server + "/app-api/pay/order/submit",
|
||||
method: 'POST',
|
||||
dataType: "json",
|
||||
contentType: "application/json",
|
||||
data: JSON.stringify({
|
||||
"id": payOrderId,
|
||||
"channelCode": 'alipay_qr'
|
||||
}),
|
||||
success: function( result ) {
|
||||
if (result.code !== 0) {
|
||||
alert('提交支付订单失败,原因:' + result.msg)
|
||||
return;
|
||||
}
|
||||
//提交支付后返回的参数
|
||||
let data = result.data.invokeResponse;
|
||||
new QRCode($("#qrcode")[0],{
|
||||
text: data.qrCode, //内容
|
||||
width:98, //宽度
|
||||
height:98, //高度
|
||||
correctLevel: 3,//二维码纠错级别
|
||||
background: "#ffffff",//背景颜色
|
||||
foreground: "#000000"//二维码颜色
|
||||
});
|
||||
|
||||
console.log("data.qrCode===",data.qrCode)
|
||||
|
||||
}
|
||||
})
|
||||
});
|
||||
</script>
|
||||
</html>
|
|
@ -1,65 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no"/>
|
||||
<title>支付测试页</title>
|
||||
<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.0/jquery.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div>点击如下按钮,发起支付的测试</div>
|
||||
<div>
|
||||
<button id="alipay_wap">支付宝手机网站支付</button>
|
||||
</div>
|
||||
<div id="dynamic_form"></div>
|
||||
</body>
|
||||
<script>
|
||||
|
||||
let shopOrderId = undefined;
|
||||
let payOrderId = undefined;
|
||||
let server = 'http://127.0.0.1:48080';
|
||||
//let server = 'http://niubi.natapp1.cc';
|
||||
|
||||
$(function() {
|
||||
// 自动发起商城订单编号
|
||||
$.ajax({
|
||||
url: server + "/app-api/shop/order/create",
|
||||
method: 'POST',
|
||||
success: function( result ) {
|
||||
if (result.code !== 0) {
|
||||
alert('创建商城订单失败,原因:' + result.msg)
|
||||
return;
|
||||
}
|
||||
shopOrderId = result.data.id;
|
||||
payOrderId = result.data.payOrderId;
|
||||
console.log("商城订单:" + shopOrderId)
|
||||
console.log("支付订单:" + payOrderId)
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
$( "#alipay_wap").on( "click", function() {
|
||||
// 提交支付
|
||||
$.ajax({
|
||||
url: server + "/app-api/pay/order/submit",
|
||||
method: 'POST',
|
||||
dataType: "json",
|
||||
contentType: "application/json",
|
||||
data: JSON.stringify({
|
||||
"id": payOrderId,
|
||||
"channelCode": 'alipay_wap'
|
||||
}),
|
||||
success: function( result ) {
|
||||
if (result.code !== 0) {
|
||||
alert('提交支付订单失败,原因:' + result.msg)
|
||||
return;
|
||||
}
|
||||
alert('点击确定,开始支付');
|
||||
//支付宝 手机WAP 返回表单,自动跳到支付宝支付页面
|
||||
let data = result.data.invokeResponse;
|
||||
$("#dynamic_form").html(data.body);
|
||||
}
|
||||
})
|
||||
});
|
||||
</script>
|
||||
</html>
|
|
@ -1,38 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no"/>
|
||||
<title>社交登陆测试页</title>
|
||||
<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.0/jquery.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div>点击如下按钮,发起登陆的测试</div>
|
||||
<div>
|
||||
<button id="wx_pub">微信公众号</button>
|
||||
</div>
|
||||
</body>
|
||||
<script>
|
||||
// let server = 'http://127.0.0.1:28080';
|
||||
let server = 'http://192.168.1.2:48080';
|
||||
|
||||
|
||||
// 微信公众号
|
||||
$( "#wx_pub").on( "click", function() {
|
||||
// 获得授权链接
|
||||
$.ajax({
|
||||
url: server + "/app-api/social-auth-redirect?type=31&redirectUri=" +
|
||||
encodeURIComponent(server + '/static/social-login2.html'), //重定向地址
|
||||
method: 'GET',
|
||||
success: function( result ) {
|
||||
if (result.code !== 0) {
|
||||
alert('获得授权链接失败,原因:' + result.msg)
|
||||
return;
|
||||
}
|
||||
// 跳转重定向
|
||||
document.location.href = result.data;
|
||||
}
|
||||
})
|
||||
});
|
||||
</script>
|
||||
</html>
|
|
@ -1,87 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no"/>
|
||||
<title>社交登陆测试页</title>
|
||||
<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.0/jquery.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div>点击如下按钮,授权登录</div>
|
||||
<div>
|
||||
手机号<input id="mobile" value="15601691300"><br>
|
||||
手机验证码<input id="smsCode">
|
||||
<button id="send_sms_code">发送手机验证码</button>
|
||||
<br>
|
||||
<button id="wx_pub">微信公众号授权登录</button>
|
||||
</div>
|
||||
</body>
|
||||
<script>
|
||||
// let server = 'http://127.0.0.1:48080';
|
||||
let server = 'http://192.168.1.2:48080';
|
||||
|
||||
let type = 31; //登录类型 微信公众号
|
||||
|
||||
// 微信公众号
|
||||
$("#wx_pub").on("click", function () {
|
||||
let code = getUrlParam("code"); // 访问授权连接后,会回调本页面地址,参数在本页面url后面
|
||||
let state = getUrlParam("state");
|
||||
console.log("获取code: " + code + ", state: " + state)
|
||||
|
||||
let data = {
|
||||
'mobile': $('#mobile').val(),
|
||||
'smsCode': $('#smsCode').val(),
|
||||
'code': code,
|
||||
'state': state,
|
||||
'type': type
|
||||
}
|
||||
|
||||
// 调用授权登录接口
|
||||
$.ajax({
|
||||
url: server + "/app-api/social-login2",
|
||||
method: 'POST',
|
||||
data: JSON.stringify(data),
|
||||
contentType: "application/json;charset=utf-8",
|
||||
dataType: "json",
|
||||
success: function( result ) {
|
||||
if (result.code !== 0) {
|
||||
alert('调用授权登录接口失败,原因:' + result.msg)
|
||||
return;
|
||||
}
|
||||
alert("授权登录成功, token: "+result.data.token)
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
// 发送手机验证码
|
||||
$("#send_sms_code").on("click", function () {
|
||||
let data = {
|
||||
'mobile': $('#mobile').val(),
|
||||
'scene': 1 // 手机号登陆 类型
|
||||
}
|
||||
$.ajax({
|
||||
url: server + "/app-api/send-sms-code",
|
||||
method: 'POST',
|
||||
data: JSON.stringify(data),
|
||||
contentType: "application/json;charset=utf-8",
|
||||
dataType: "json",
|
||||
success: function (result) {
|
||||
if (result.code !== 0) {
|
||||
alert('发送手机验证码失败,原因:' + result.msg)
|
||||
return;
|
||||
}
|
||||
alert("发送成功, 请查看日志");
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
//获取url中的参数
|
||||
function getUrlParam(name) {
|
||||
var reg = new RegExp("(^|&)" + name + "=([^&]*)(&|$)"); //构造一个含有目标参数的正则表达式对象
|
||||
var r = window.location.search.substr(1).match(reg); //匹配目标参数
|
||||
if (r != null) return unescape(r[2]);
|
||||
return null; //返回参数值
|
||||
}
|
||||
</script>
|
||||
</html>
|
|
@ -57,6 +57,7 @@
|
|||
"jsencrypt": "3.3.1",
|
||||
"min-dash": "3.5.2",
|
||||
"nprogress": "0.2.0",
|
||||
"qrcode.vue": "^1.7.0",
|
||||
"quill": "1.3.7",
|
||||
"screenfull": "5.0.2",
|
||||
"sortablejs": "1.10.2",
|
||||
|
|
|
@ -0,0 +1,35 @@
|
|||
import request from '@/utils/request'
|
||||
|
||||
// 创建示例订单
|
||||
export function createDemoOrder(data) {
|
||||
return request({
|
||||
url: '/pay/demo-order/create',
|
||||
method: 'post',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
// 获得示例订单
|
||||
export function getDemoOrder(id) {
|
||||
return request({
|
||||
url: '/pay/demo-order/get?id=' + id,
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
// 获得示例订单分页
|
||||
export function getDemoOrderPage(query) {
|
||||
return request({
|
||||
url: '/pay/demo-order/page',
|
||||
method: 'get',
|
||||
params: query
|
||||
})
|
||||
}
|
||||
|
||||
// 退款示例订单
|
||||
export function refundDemoOrder(id) {
|
||||
return request({
|
||||
url: '/pay/demo-order/refund?id=' + id,
|
||||
method: 'put'
|
||||
})
|
||||
}
|
|
@ -1,23 +1,5 @@
|
|||
import request from '@/utils/request'
|
||||
|
||||
// 创建支付订单
|
||||
export function createOrder(data) {
|
||||
return request({
|
||||
url: '/pay/order/create',
|
||||
method: 'post',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
// 更新支付订单
|
||||
export function updateOrder(data) {
|
||||
return request({
|
||||
url: '/pay/order/update',
|
||||
method: 'put',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
// 删除支付订单
|
||||
export function deleteOrder(id) {
|
||||
return request({
|
||||
|
@ -34,6 +16,23 @@ export function getOrder(id) {
|
|||
})
|
||||
}
|
||||
|
||||
// 获得支付订单的明细
|
||||
export function getOrderDetail(id) {
|
||||
return request({
|
||||
url: '/pay/order/get-detail?id=' + id,
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
// 提交支付订单
|
||||
export function submitOrder(data) {
|
||||
return request({
|
||||
url: '/pay/order/submit',
|
||||
method: 'post',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
// 获得支付订单分页
|
||||
export function getOrderPage(query) {
|
||||
return request({
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
<svg t="1627279997305" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="11904" width="40" height="40"><path d="M938.7008 669.525333L938.7008 249.412267c0-90.555733-73.5232-164.078933-164.1472-164.078933L249.378133 85.333333c-90.555733 0-164.078933 73.48906699-164.078933 164.078933l0 525.2096c0 90.555733 73.454933 164.078933 164.07893301 164.078933l525.20959999 0c80.725333 0 147.8656-58.368 161.553067-135.099733-43.52-18.8416-232.106667-100.283733-330.376533-147.182933-74.786133 90.589867-153.088 144.930133-271.121067 144.930133s-196.81279999-72.704-187.357867-161.655467c6.2464-58.402133 46.2848-153.9072 220.296533-137.5232 91.682133 8.6016 133.666133 25.736533 208.418133 50.414933 19.3536-35.4304 35.4304-74.513067 47.616-116.0192L292.0448 436.565333l0-32.8704 164.0448 0 0-58.9824L256 344.712533l1e-8-36.181333 200.12373299 0L456.123733 223.3344c0 0 1.809067-13.312 16.520533-13.31200001l82.056533 1e-8 0 98.474667 213.333333 0 0 36.181333-213.333333 1e-8 0 58.98239999 174.045867 0c-16.00853301 65.1264-40.277333 124.962133-70.690133 177.220267C708.608 599.176533 938.7008 669.525333 938.7008 669.525333L938.7008 669.525333 938.7008 669.525333 938.7008 669.525333zM321.57013299 744.994133c-124.7232 0-144.452267-78.7456-137.83039999-111.65013299 6.5536-32.733867 42.666667-75.502933 112.0256-75.50293301 79.6672 0 151.04 20.445867 236.714667 62.088533C472.302933 698.333867 398.370133 744.994133 321.57013299 744.994133L321.57013299 744.994133 321.57013299 744.994133zM321.57013299 744.994133" fill="#1296db" p-id="11905"></path></svg>
|
After Width: | Height: | Size: 1.6 KiB |
|
@ -0,0 +1,2 @@
|
|||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1627279586085" class="icon" viewBox="0 0 1036 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="6737" xmlns:xlink="http://www.w3.org/1999/xlink" width="40.46875" height="40"><defs><style type="text/css">@font-face { font-family: feedback-iconfont; src: url("//at.alicdn.com/t/font_1031158_1uhr8ri0pk5.eot?#iefix") format("embedded-opentype"), url("//at.alicdn.com/t/font_1031158_1uhr8ri0pk5.woff2") format("woff2"), url("//at.alicdn.com/t/font_1031158_1uhr8ri0pk5.woff") format("woff"), url("//at.alicdn.com/t/font_1031158_1uhr8ri0pk5.ttf") format("truetype"), url("//at.alicdn.com/t/font_1031158_1uhr8ri0pk5.svg#iconfont") format("svg"); }
|
||||
</style></defs><path d="M27.587124 336.619083h69.148134a13.978733 13.978733 0 0 0 13.79235-13.978733V13.989916A13.978733 13.978733 0 0 0 96.735258 0.011183H27.587124a13.978733 13.978733 0 0 0-13.792351 13.978733v308.650434a13.978733 13.978733 0 0 0 13.792351 13.978733z m165.880969 0h27.584701a13.978733 13.978733 0 0 0 13.79235-13.978733V13.989916a13.978733 13.978733 0 0 0-13.79235-13.978733h-27.584701a13.978733 13.978733 0 0 0-13.79235 13.978733v308.650434a13.978733 13.978733 0 0 0 13.79235 13.978733z m138.109886 322.629167h-110.525185a27.771084 27.771084 0 0 0-27.584701 28.14385v111.829867a27.771084 27.771084 0 0 0 27.584701 28.14385h110.525185a27.957467 27.957467 0 0 0 27.584701-28.14385v-111.829867a27.957467 27.957467 0 0 0-27.584701-28.14385z m484.596091-322.629167h27.584701a13.978733 13.978733 0 0 0 13.79235-13.978733V13.989916a13.978733 13.978733 0 0 0-14.537883-13.978733h-27.5847a13.978733 13.978733 0 0 0-13.978734 13.978733v308.650434a13.978733 13.978733 0 0 0 13.978734 13.978733z m-469.871825 0H428.68358a13.978733 13.978733 0 0 0 13.792351-13.978733V13.989916A13.978733 13.978733 0 0 0 428.68358 0.011183h-83.126867a13.978733 13.978733 0 0 0-13.792351 13.978733v308.650434a13.978733 13.978733 0 0 0 13.792351 13.978733z m594.189361 0h69.148134a13.978733 13.978733 0 0 0 13.792351-13.978733V13.989916a13.978733 13.978733 0 0 0-14.537883-13.978733h-69.148135a13.978733 13.978733 0 0 0-13.79235 13.978733v308.650434a13.978733 13.978733 0 0 0 13.79235 13.978733z m-412.279444 126.181367H66.91396A67.470687 67.470687 0 0 0 0.002423 530.830286v425.139878a67.470687 67.470687 0 0 0 66.911537 68.029836h418.802853a67.470687 67.470687 0 0 0 66.911537-68.029836V487.775787a24.788954 24.788954 0 0 0-24.416188-24.975337z m-58.337914 433.899885a42.681733 42.681733 0 0 1-42.495349 43.054498H125.438257a42.681733 42.681733 0 0 1-42.495349-43.054498V590.100115a42.681733 42.681733 0 0 1 42.495349-43.054498h301.940642a42.681733 42.681733 0 0 1 42.495349 43.054498z m525.22761-433.899885a41.749817 41.749817 0 0 0-41.377051 42.122583v55.914934a41.377051 41.377051 0 1 0 82.940485 0v-55.914934a41.749817 41.749817 0 0 0-41.563434-42.122583z m0 223.659734a41.749817 41.749817 0 0 0-41.377051 42.122584V894.65012a45.477479 45.477479 0 0 1-45.291096 45.850246h-159.730327a43.240882 43.240882 0 0 0-43.613649 37.276622A41.9362 41.9362 0 0 0 745.534871 1024h233.538039a57.778765 57.778765 0 0 0 57.405999-58.337914V729.3283a41.749817 41.749817 0 0 0-41.377051-41.9362zM732.488053 322.64035V13.989916a13.978733 13.978733 0 0 0-13.79235-13.978733h-82.940485a13.978733 13.978733 0 0 0-13.79235 13.978733v308.650434a13.978733 13.978733 0 0 0 13.79235 13.978733h82.940485a13.978733 13.978733 0 0 0 13.79235-13.978733zM532.126208 0.011183c-11.36937 0-20.688525 6.337026-20.688526 13.978733v308.650434c0 7.828091 9.319156 13.978733 20.688526 13.978733s20.688525-6.337026 20.688525-13.978733V13.989916c0-7.641708-9.319156-13.978733-20.688525-13.978733z" p-id="6738" fill="#1977FD"></path><path d="M745.534871 462.80045a41.749817 41.749817 0 0 0-41.377051 42.122583v252.549117a41.377051 41.377051 0 1 0 82.940485 0V504.923033A41.749817 41.749817 0 0 0 745.534871 462.80045" p-id="6739" fill="#1977FD"></path></svg>
|
After Width: | Height: | Size: 3.9 KiB |
|
@ -0,0 +1 @@
|
|||
<svg t="1627279878333" class="icon" viewBox="0 0 1285 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="8535" width="40" height="40"><path d="M1141.76 855.04h-286.72c0 40.96 30.72 71.68 71.68 71.68h107.52c20.48 0 35.84 15.36 35.84 35.84s-15.36 35.84-35.84 35.84h-783.36c-20.48 0-35.84-15.36-35.84-35.84s15.36-35.84 35.84-35.84h107.52c40.96 0 71.68-30.72 71.68-71.68h-286.72c-76.8 0-143.36-61.44-143.36-143.36v-568.32c0-76.8 61.44-143.36 143.36-143.36h993.28c76.8 0 143.36 61.44 143.36 143.36v568.32c5.12 76.8-56.32 143.36-138.24 143.36z m71.68-711.68c0-40.96-30.72-71.68-71.68-71.68h-993.28c-40.96 0-71.68 30.72-71.68 71.68v568.32c0 40.96 30.72 71.68 71.68 71.68h993.28c40.96 0 71.68-30.72 71.68-71.68v-568.32z m-143.36 568.32h-855.04c-40.96 0-71.68-30.72-71.68-71.68v-424.96c0-40.96 30.72-71.68 71.68-71.68h855.04c40.96 0 71.68 30.72 71.68 71.68v424.96c0 40.96-30.72 71.68-71.68 71.68z" p-id="8536" fill="#1977FD"></path></svg>
|
After Width: | Height: | Size: 939 B |
|
@ -0,0 +1,2 @@
|
|||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1627279238245" class="icon" viewBox="0 0 1115 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4112" width="43.5546875" height="40" xmlns:xlink="http://www.w3.org/1999/xlink"><defs><style type="text/css">@font-face { font-family: feedback-iconfont; src: url("//at.alicdn.com/t/font_1031158_1uhr8ri0pk5.eot?#iefix") format("embedded-opentype"), url("//at.alicdn.com/t/font_1031158_1uhr8ri0pk5.woff2") format("woff2"), url("//at.alicdn.com/t/font_1031158_1uhr8ri0pk5.woff") format("woff"), url("//at.alicdn.com/t/font_1031158_1uhr8ri0pk5.ttf") format("truetype"), url("//at.alicdn.com/t/font_1031158_1uhr8ri0pk5.svg#iconfont") format("svg"); }
|
||||
</style></defs><path d="M751.388 68.267a34.133 34.133 0 0 1 0-68.267h227.556a91.022 91.022 0 0 1 91.022 91.022v227.556a34.133 34.133 0 1 1-68.266 0V91.022a22.756 22.756 0 0 0-22.756-22.755H751.388M1001.7 705.422a34.133 34.133 0 0 1 68.266 0v227.556A91.022 91.022 0 0 1 978.944 1024H748.885a34.133 34.133 0 0 1 0-68.267H978.49a22.756 22.756 0 0 0 22.755-22.755V705.422M364.09 955.733a34.133 34.133 0 1 1 0 68.267H136.533a91.022 91.022 0 0 1-91.022-91.022V705.422a34.133 34.133 0 0 1 68.267 0v227.556a22.756 22.756 0 0 0 22.755 22.755H364.09M113.778 318.578a34.133 34.133 0 1 1-68.267 0V91.022A91.022 91.022 0 0 1 136.533 0H364.09a34.133 34.133 0 0 1 0 68.267H136.533a22.756 22.756 0 0 0-22.755 22.755v227.556M34.133 477.867a34.133 34.133 0 0 0 0 68.266h168.619v-68.266z m1046.756 0H912.27v68.266h168.619a34.133 34.133 0 0 0 0-68.266zM202.752 157.24h709.746v320.627H202.752z m0 388.893h709.746V866.76H202.752z" fill="#1977FD" p-id="4113"></path></svg>
|
After Width: | Height: | Size: 1.7 KiB |
|
@ -0,0 +1 @@
|
|||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1645964864184" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="8460" xmlns:xlink="http://www.w3.org/1999/xlink" width="40" height="40"><defs><style type="text/css"></style></defs><path d="M768.3 0 255.7 0c-70.8 0-128.1 57.4-128.1 128.1l0 767.8c0 70.8 57.4 128.1 128.1 128.1L512 1024l256.3 0c70.8 0 128.1-57.4 128.1-128.1L896.4 128.1C896.4 57.3 839 0 768.3 0zM383.9 96.1c0-17.7 14.3-32 32-32l192.2 0c17.7 0 32 14.3 32 32l0 0c0 17.7-14.3 32-32 32L415.9 128.1C398.2 128.1 383.9 113.8 383.9 96.1L383.9 96.1zM512 959.9 512 959.9 512 959.9c-35.4 0-64.1-28.8-64.1-64.1 0-35.4 28.7-64.1 64.1-64.1l0 0 0 0c35.4 0 64.1 28.7 64.1 64.1C576.1 931.1 547.4 959.9 512 959.9zM832.3 755.6c0 6.7-5.4 12.2-12.2 12.2L203.9 767.8c-6.7 0-12.2-5.4-12.2-12.2L191.7 204.3c0-6.7 5.4-12.2 12.2-12.2l616.3 0c6.7 0 12.2 5.4 12.2 12.2L832.4 755.6z" p-id="8461" fill="#1977FD"></path></svg>
|
After Width: | Height: | Size: 1.0 KiB |
|
@ -0,0 +1 @@
|
|||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1676209854312" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="3033" xmlns:xlink="http://www.w3.org/1999/xlink" width="128" height="128"><path d="M173.077333 362.666667l91.114667-214.677334a65.6 65.6 0 0 1 86.016-34.773333c11.584 4.906667 24.96 10.282667 40.896 16.448 8.277333 3.2 16.789333 6.464 27.904 10.666667 28.202667 10.709333 39.296 14.933333 46.144 17.642666l51.477333-51.669333c28.181333-28.16 74.112-27.946667 102.570667 0.533333l195.925333 195.925334c16.426667 16.426667 23.445333 38.634667 21.056 59.904H896a42.666667 42.666667 0 0 1 42.666667 42.666666v490.666667a42.666667 42.666667 0 0 1-42.666667 42.666667H128a42.666667 42.666667 0 0 1-42.666667-42.666667V405.333333a42.666667 42.666667 0 0 1 42.666667-42.666666h45.077333z m48.96 0h39.104l169.194667-169.770667-27.328-10.389333c-11.2-4.245333-19.818667-7.530667-28.224-10.794667a1459.2 1459.2 0 0 1-42.197333-17.002667 20.522667 20.522667 0 0 0-26.901334 10.88L222.037333 362.666667z m108.842667 0h454.954667a23.509333 23.509333 0 0 0-5.290667-25.322667l-195.925333-195.925333a23.36 23.36 0 0 0-33.024-0.213334L330.88 362.666667zM128 405.333333v490.666667h768V405.333333H128z m597.333333 320a85.333333 85.333333 0 1 1 0-170.666666 85.333333 85.333333 0 0 1 0 170.666666z m0-42.666666a42.666667 42.666667 0 1 0 0-85.333334 42.666667 42.666667 0 0 0 0 85.333334z" fill="#4296d5" p-id="3034"></path></svg>
|
After Width: | Height: | Size: 1.5 KiB |
|
@ -0,0 +1,2 @@
|
|||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1627279375144" class="icon" viewBox="0 0 1115 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4399" width="43.5546875" height="40" xmlns:xlink="http://www.w3.org/1999/xlink"><defs><style type="text/css">@font-face { font-family: feedback-iconfont; src: url("//at.alicdn.com/t/font_1031158_1uhr8ri0pk5.eot?#iefix") format("embedded-opentype"), url("//at.alicdn.com/t/font_1031158_1uhr8ri0pk5.woff2") format("woff2"), url("//at.alicdn.com/t/font_1031158_1uhr8ri0pk5.woff") format("woff"), url("//at.alicdn.com/t/font_1031158_1uhr8ri0pk5.ttf") format("truetype"), url("//at.alicdn.com/t/font_1031158_1uhr8ri0pk5.svg#iconfont") format("svg"); }
|
||||
</style></defs><path d="M751.388 68.267a34.133 34.133 0 0 1 0-68.267h227.556a91.022 91.022 0 0 1 91.022 91.022v227.556a34.133 34.133 0 1 1-68.266 0V91.022a22.756 22.756 0 0 0-22.756-22.755H751.388M1001.7 705.422a34.133 34.133 0 0 1 68.266 0v227.556A91.022 91.022 0 0 1 978.944 1024H748.885a34.133 34.133 0 0 1 0-68.267H978.49a22.756 22.756 0 0 0 22.755-22.755V705.422M364.09 955.733a34.133 34.133 0 1 1 0 68.267H136.533a91.022 91.022 0 0 1-91.022-91.022V705.422a34.133 34.133 0 0 1 68.267 0v227.556a22.756 22.756 0 0 0 22.755 22.755H364.09M113.778 318.578a34.133 34.133 0 1 1-68.267 0V91.022A91.022 91.022 0 0 1 136.533 0H364.09a34.133 34.133 0 0 1 0 68.267H136.533a22.756 22.756 0 0 0-22.755 22.755v227.556M34.133 477.867a34.133 34.133 0 0 0 0 68.266h168.619v-68.266z m1046.756 0H912.27v68.266h168.619a34.133 34.133 0 0 0 0-68.266zM202.752 157.24h709.746v320.627H202.752z m0 388.893h709.746V866.76H202.752z" fill="#04C361" p-id="4400"></path></svg>
|
After Width: | Height: | Size: 1.7 KiB |
|
@ -0,0 +1 @@
|
|||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1676209433089" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2990" xmlns:xlink="http://www.w3.org/1999/xlink" width="128" height="128"><path d="M608.6 290.3c67.1 0 121.7 50.5 121.7 112.9 0 19.4-5.6 38.4-15.7 55.5-15.3 25-39.8 43.5-69.4 52.3-7.9 2.3-13.9 3.2-19.4 3.2-13 0-23.1-10.2-23.1-23.1 0-13 10.2-23.1 23.1-23.1 0.9 0 2.8 0 5.1-0.9 19.9-5.6 35.6-17.1 44.4-32.4 6-9.7 8.8-20.4 8.8-31.5 0-36.6-33.8-66.6-75-66.6-14.4 0-28.2 3.7-40.7 10.6-21.8 12.5-34.7 33.3-34.7 56v193.9c0 39.3-21.8 75.4-57.9 95.8-19.4 11.1-41.2 16.7-63.4 16.7-67.1 0-121.7-50.5-121.7-112.9 0-19.4 5.6-38.4 15.7-55.5 15.3-25 39.8-43.5 69.4-52.3 8.3-2.3 13.9-3.2 19.4-3.2 13 0 23.1 10.2 23.1 23.1 0 13-10.2 23.1-23.1 23.1-0.9 0-2.8 0-5.1 0.9-19.9 6-35.6 17.6-44.4 32.4-6 9.7-8.8 20.4-8.8 31.5 0 36.6 33.8 66.6 75.4 66.6 14.4 0 28.2-3.7 40.7-10.6 21.8-12.5 34.7-33.3 34.7-56V403.3c0-39.3 21.8-75.4 57.9-95.8 19-11.6 40.7-17.2 63-17.2zM510.8 929c231.1 0 418.4-187.3 418.4-418.4S741.9 92.1 510.8 92.1 92.4 279.5 92.4 510.6 279.7 929 510.8 929z m0 22C267.5 951 70.3 753.8 70.3 510.6S267.5 70.1 510.8 70.1s440.5 197.2 440.5 440.5S754.1 951 510.8 951z" p-id="2991" fill="#58bf6b"></path></svg>
|
After Width: | Height: | Size: 1.3 KiB |
|
@ -0,0 +1,2 @@
|
|||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1627279797174" class="icon" viewBox="0 0 1260 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="7665" xmlns:xlink="http://www.w3.org/1999/xlink" width="49.21875" height="40"><defs><style type="text/css">@font-face { font-family: feedback-iconfont; src: url("//at.alicdn.com/t/font_1031158_1uhr8ri0pk5.eot?#iefix") format("embedded-opentype"), url("//at.alicdn.com/t/font_1031158_1uhr8ri0pk5.woff2") format("woff2"), url("//at.alicdn.com/t/font_1031158_1uhr8ri0pk5.woff") format("woff"), url("//at.alicdn.com/t/font_1031158_1uhr8ri0pk5.ttf") format("truetype"), url("//at.alicdn.com/t/font_1031158_1uhr8ri0pk5.svg#iconfont") format("svg"); }
|
||||
</style></defs><path d="M797.14798 481.753a269.194 269.194 0 0 0 102.892-211.929C900.03998 120.99 779.02998 0 630.15698 0 481.28298 0 360.27398 120.99 360.27398 269.824c0 85.878 40.33 162.462 102.912 211.929A450.974 450.974 0 0 0 309.84198 582.774c-85.543 85.524-132.608 199.208-132.608 320.236 0 25.01 0 51.712 0.197 76.367a44.898 44.898 0 0 0 44.82 44.623h816.01a44.8 44.8 0 0 0 44.82-44.623V903.01c0-121.009-47.066-234.732-132.609-320.236a451.072 451.072 0 0 0-153.344-101.021z" p-id="7666" fill="#04C361"></path><path d="M1186.18898 580.391A378.644 378.644 0 0 0 1061.81198 473.03a223.783 223.783 0 0 0 64.237-157.657c0-49.742-15.872-96.67-45.746-136.074A225.34 225.34 0 0 0 964.70998 99.9a37.297 37.297 0 0 0-46.14 25.718c-5.592 19.89 5.79 40.724 25.6 46.356 63.114 18.196 107.363 77.135 107.363 143.4a148.913 148.913 0 0 1-81.23 133.06 38.065 38.065 0 0 0-20.363 36.608c1.32 15.203 11.58 28.16 25.975 32.65 125.479 39.601 209.703 155.038 209.703 287.173v63.074c0 20.638 16.62 37.534 37.16 37.711h0.196a37.396 37.396 0 0 0 37.337-37.336V805.06c-0.197-81.644-25.777-159.35-74.142-224.69z m-901.77-62.503a36.982 36.982 0 0 0 25.955-32.65 37.455 37.455 0 0 0-20.362-36.628 148.913 148.913 0 0 1-81.231-133.06c0-66.245 44.071-125.184 107.382-143.4a37.612 37.612 0 0 0 25.58-46.356 37.376 37.376 0 0 0-46.139-25.718 225.32 225.32 0 0 0-115.593 79.4 223.252 223.252 0 0 0-45.746 136.074c0 60.258 23.533 116.381 64.237 157.676A380.475 380.475 0 0 0 74.14498 580.569 373.839 373.839 0 0 0 0.00198 805.258v63.232c0 20.657 16.798 37.356 37.356 37.356h0.197a37.317 37.317 0 0 0 37.14-37.73V805.06c0-132.332 84.401-247.769 209.723-287.173z" p-id="7667" fill="#04C361"></path></svg>
|
After Width: | Height: | Size: 2.4 KiB |
|
@ -221,6 +221,21 @@ export const constantRoutes = [
|
|||
component: (resolve) => require(['@/views/mall/trade/order/detail'], resolve)
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: '/pay',
|
||||
component: Layout,
|
||||
hidden: true,
|
||||
children: [{
|
||||
path: 'order/submit',
|
||||
name: 'PayOrderSubmit',
|
||||
hidden: true,
|
||||
meta: {
|
||||
title: '收银台',
|
||||
noCache: true
|
||||
},
|
||||
component: (resolve) => require(['@/views/pay/order/submit'], resolve)
|
||||
}]
|
||||
}
|
||||
]
|
||||
|
||||
|
|
|
@ -148,6 +148,28 @@ export const PayChannelEnum = {
|
|||
"code": "alipay_qr",
|
||||
"name": "支付宝扫码支付"
|
||||
},
|
||||
ALIPAY_BAR: {
|
||||
"code": "alipay_bar",
|
||||
"name": "支付宝条码支付"
|
||||
},
|
||||
}
|
||||
|
||||
/**
|
||||
* 支付的展示模式每局
|
||||
*/
|
||||
export const PayDisplayModeEnum = {
|
||||
URL: {
|
||||
"mode": "url",
|
||||
},
|
||||
IFRAME: {
|
||||
"mode": "iframe",
|
||||
},
|
||||
FORM: {
|
||||
"mode": "form"
|
||||
},
|
||||
QR_CODE: {
|
||||
"mode": "qr_code"
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -172,7 +194,7 @@ export const PayOrderStatusEnum = {
|
|||
},
|
||||
CLOSED: {
|
||||
status: 20,
|
||||
name: '未支付'
|
||||
name: '支付关闭'
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -53,11 +53,6 @@
|
|||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="商户名称" align="center" prop="payMerchant.name"/>
|
||||
<el-table-column label="创建时间" align="center" prop="createTime" width="180">
|
||||
<template v-slot="scope">
|
||||
<span>{{ parseTime(scope.row.createTime) }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="支付宝配置" align="center">
|
||||
<el-table-column :label="payChannelEnum.ALIPAY_APP.name" align="center">
|
||||
<template v-slot="scope">
|
||||
|
@ -107,6 +102,18 @@
|
|||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column :label="payChannelEnum.ALIPAY_BAR.name" align="center">
|
||||
<template v-slot="scope">
|
||||
<el-button type="success" icon="el-icon-check" circle
|
||||
v-if="judgeChannelExist(scope.row.channelCodes,payChannelEnum.ALIPAY_BAR.code)"
|
||||
@click="handleUpdateChannel(scope.row,payChannelEnum.ALIPAY_BAR.code,payType.ALIPAY)">
|
||||
</el-button>
|
||||
<el-button v-else
|
||||
type="danger" icon="el-icon-close" circle
|
||||
@click="handleCreateChannel(scope.row,payChannelEnum.ALIPAY_BAR.code,payType.ALIPAY)">
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table-column>
|
||||
<el-table-column label="微信配置" align="center">
|
||||
<el-table-column :label="payChannelEnum.WX_LITE.name" align="center">
|
||||
|
|
|
@ -0,0 +1,214 @@
|
|||
<template>
|
||||
<div class="app-container">
|
||||
<!-- 操作工具栏 -->
|
||||
<el-row :gutter="10" class="mb8">
|
||||
<el-col :span="1.5">
|
||||
<el-button type="primary" plain icon="el-icon-plus" size="mini" @click="handleAdd">发起订单</el-button>
|
||||
</el-col>
|
||||
<right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
|
||||
</el-row>
|
||||
|
||||
<!-- 列表 -->
|
||||
<el-table v-loading="loading" :data="list">
|
||||
<el-table-column label="订单编号" align="center" prop="id" />
|
||||
<el-table-column label="用户编号" align="center" prop="userId" />
|
||||
<el-table-column label="商品名字" align="center" prop="spuName" />
|
||||
<el-table-column label="支付价格" align="center" prop="price">
|
||||
<template v-slot="scope">
|
||||
<span>¥{{ (scope.row.price / 100.0).toFixed(2) }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="退款金额" align="center" prop="refundPrice">
|
||||
<template v-slot="scope">
|
||||
<span>¥{{ (scope.row.refundPrice / 100.0).toFixed(2) }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="创建时间" align="center" prop="createTime" width="180">
|
||||
<template v-slot="scope">
|
||||
<span>{{ parseTime(scope.row.createTime) }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="支付单号" align="center" prop="payOrderId" />
|
||||
<el-table-column label="是否支付" align="center" prop="payed">
|
||||
<template v-slot="scope">
|
||||
<dict-tag :type="DICT_TYPE.INFRA_BOOLEAN_STRING" :value="scope.row.payed" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="支付时间" align="center" prop="payTime" width="180">
|
||||
<template v-slot="scope">
|
||||
<span>{{ parseTime(scope.row.payTime) }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="退款时间" align="center" prop="refundTime" width="180">
|
||||
<template v-slot="scope">
|
||||
<span>{{ parseTime(scope.row.refundTime) }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
|
||||
<template v-slot="scope">
|
||||
<el-button size="mini" type="text" icon="el-icon-edit" @click="handlePay(scope.row)"
|
||||
v-if="!scope.row.payed">前往支付</el-button>
|
||||
<el-button size="mini" type="text" icon="el-icon-delete" @click="handleRefund(scope.row)"
|
||||
v-if="scope.row.payed && !scope.row.payRefundId">发起退款</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<!-- 分页组件 -->
|
||||
<pagination v-show="total > 0" :total="total" :page.sync="queryParams.pageNo" :limit.sync="queryParams.pageSize"
|
||||
@pagination="getList"/>
|
||||
|
||||
<!-- 对话框(添加 / 修改) -->
|
||||
<el-dialog :title="title" :visible.sync="open" width="500px" v-dialogDrag append-to-body>
|
||||
<el-form ref="form" :model="form" :rules="rules" label-width="80px">
|
||||
<el-form-item label="商品" prop="spuId">
|
||||
<el-select v-model="form.spuId" placeholder="请输入下单商品" clearable size="small" style="width: 380px" >
|
||||
<el-option v-for="item in spus" :key="item.id" :label="item.name" :value="item.id">
|
||||
<span style="float: left">{{ item.name}}</span>
|
||||
<span style="float: right; color: #8492a6; font-size: 13px">¥{{ (item.price / 100.0).toFixed(2) }}</span>
|
||||
</el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<div slot="footer" class="dialog-footer">
|
||||
<el-button type="primary" @click="submitForm">确 定</el-button>
|
||||
<el-button @click="cancel">取 消</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {createDemoOrder, getDemoOrderPage, refundDemoOrder} from "@/api/pay/demo";
|
||||
import {deleteMerchant} from "@/api/pay/merchant";
|
||||
|
||||
export default {
|
||||
name: "PayDemoOrder",
|
||||
components: {
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
// 遮罩层
|
||||
loading: true,
|
||||
// 显示搜索条件
|
||||
showSearch: true,
|
||||
// 总条数
|
||||
total: 0,
|
||||
// 示例订单列表
|
||||
list: [],
|
||||
// 弹出层标题
|
||||
title: "",
|
||||
// 是否显示弹出层
|
||||
open: false,
|
||||
// 查询参数
|
||||
queryParams: {
|
||||
pageNo: 1,
|
||||
pageSize: 10,
|
||||
},
|
||||
// 表单参数
|
||||
form: {},
|
||||
// 表单校验
|
||||
rules: {
|
||||
spuId: [{ required: true, message: "商品编号不能为空", trigger: "blur" }],
|
||||
},
|
||||
// 商品数组
|
||||
spus: [{
|
||||
id: 1,
|
||||
name: '华为手机',
|
||||
price: 1,
|
||||
}, {
|
||||
id: 2,
|
||||
name: '小米电视',
|
||||
price: 10,
|
||||
}, {
|
||||
id: 3,
|
||||
name: '苹果手表',
|
||||
price: 100,
|
||||
}, {
|
||||
id: 4,
|
||||
name: '华硕笔记本',
|
||||
price: 1000,
|
||||
}, {
|
||||
id: 5,
|
||||
name: '蔚来汽车',
|
||||
price: 200000,
|
||||
}]
|
||||
};
|
||||
},
|
||||
created() {
|
||||
this.getList();
|
||||
},
|
||||
methods: {
|
||||
/** 查询列表 */
|
||||
getList() {
|
||||
this.loading = true;
|
||||
// 执行查询
|
||||
getDemoOrderPage(this.queryParams).then(response => {
|
||||
this.list = response.data.list;
|
||||
this.total = response.data.total;
|
||||
this.loading = false;
|
||||
});
|
||||
},
|
||||
/** 取消按钮 */
|
||||
cancel() {
|
||||
this.open = false;
|
||||
this.reset();
|
||||
},
|
||||
/** 表单重置 */
|
||||
reset() {
|
||||
this.form = {
|
||||
spuId: undefined,
|
||||
};
|
||||
this.resetForm("form");
|
||||
},
|
||||
/** 搜索按钮操作 */
|
||||
handleQuery() {
|
||||
this.queryParams.pageNo = 1;
|
||||
this.getList();
|
||||
},
|
||||
/** 重置按钮操作 */
|
||||
resetQuery() {
|
||||
this.resetForm("queryForm");
|
||||
this.handleQuery();
|
||||
},
|
||||
/** 新增按钮操作 */
|
||||
handleAdd() {
|
||||
this.reset();
|
||||
this.open = true;
|
||||
this.title = "发起订单";
|
||||
},
|
||||
/** 提交按钮 */
|
||||
submitForm() {
|
||||
this.$refs["form"].validate(valid => {
|
||||
if (!valid) {
|
||||
return;
|
||||
}
|
||||
// 添加的提交
|
||||
createDemoOrder(this.form).then(response => {
|
||||
this.$modal.msgSuccess("新增成功");
|
||||
this.open = false;
|
||||
this.getList();
|
||||
});
|
||||
});
|
||||
},
|
||||
/** 支付按钮操作 */
|
||||
handlePay(row) {
|
||||
this.$router.push({
|
||||
name: 'PayOrderSubmit',
|
||||
query:{
|
||||
id: row.payOrderId
|
||||
}
|
||||
})
|
||||
},
|
||||
/** 退款按钮操作 */
|
||||
handleRefund(row) {
|
||||
const id = row.id;
|
||||
this.$modal.confirm('是否确认退款编号为"' + id + '"的示例订单?').then(function() {
|
||||
return refundDemoOrder(id);
|
||||
}).then(() => {
|
||||
this.getList();
|
||||
this.$modal.msgSuccess("发起退款成功!");
|
||||
}).catch(() => {});
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
|
@ -215,7 +215,7 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import {getOrder, getOrderPage, exportOrderExcel} from "@/api/pay/order";
|
||||
import { getOrderDetail, getOrderPage, exportOrderExcel} from "@/api/pay/order";
|
||||
import {getMerchantListByName} from "@/api/pay/merchant";
|
||||
import {getAppListByMerchantId} from "@/api/pay/app";
|
||||
import {DICT_TYPE, getDictDatas} from "@/utils/dict";
|
||||
|
@ -250,7 +250,7 @@ const defaultOrderDetail = {
|
|||
};
|
||||
|
||||
export default {
|
||||
name: "Order",
|
||||
name: "PayOrder",
|
||||
components: {},
|
||||
data() {
|
||||
return {
|
||||
|
@ -364,7 +364,7 @@ export default {
|
|||
*/
|
||||
handleQueryDetails(row) {
|
||||
this.orderDetail = JSON.parse(JSON.stringify(defaultOrderDetail));
|
||||
getOrder(row.id).then(response => {
|
||||
getOrderDetail(row.id).then(response => {
|
||||
this.orderDetail = response.data;
|
||||
if (response.data.payOrderExtension === null) {
|
||||
this.orderDetail.payOrderExtension = Object.assign(defaultOrderDetail.payOrderExtension, {});
|
||||
|
|
|
@ -0,0 +1,397 @@
|
|||
<template>
|
||||
<div class="app-container">
|
||||
<!-- 支付信息 -->
|
||||
<el-card v-loading="loading">
|
||||
<el-descriptions title="支付信息" :column="3" border>
|
||||
<el-descriptions-item label="支付单号">{{ payOrder.id }}</el-descriptions-item>
|
||||
<el-descriptions-item label="商品标题">{{ payOrder.subject }}</el-descriptions-item>
|
||||
<el-descriptions-item label="商品内容">{{ payOrder.body }}</el-descriptions-item>
|
||||
<el-descriptions-item label="支付金额">¥{{ (payOrder.amount / 100.0).toFixed(2) }}</el-descriptions-item>
|
||||
<el-descriptions-item label="创建时间">{{ parseTime(payOrder.createTime) }}</el-descriptions-item>
|
||||
<el-descriptions-item label="过期时间">{{ parseTime(payOrder.expireTime) }}</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
</el-card>
|
||||
|
||||
<!-- 支付选择框 -->
|
||||
<el-card style="margin-top: 10px" v-loading="submitLoading" element-loading-text="提交支付中...">
|
||||
<!-- 支付宝 -->
|
||||
<el-descriptions title="选择支付宝支付">
|
||||
</el-descriptions>
|
||||
<div class="pay-channel-container">
|
||||
<div class="box" v-for="channel in aliPayChannels" :key="channel.code" @click="submit(channel.code)">
|
||||
<img :src="icons[channel.code]">
|
||||
<div class="title">{{ channel.name }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 微信支付 -->
|
||||
<el-descriptions title="选择微信支付" style="margin-top: 20px;" />
|
||||
<div class="pay-channel-container">
|
||||
<div class="box" v-for="channel in wxPayChannels" :key="channel.code">
|
||||
<img :src="icons[channel.code]">
|
||||
<div class="title">{{ channel.name }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 其它支付 -->
|
||||
<el-descriptions title="选择其它支付" style="margin-top: 20px;" />
|
||||
<div class="pay-channel-container">
|
||||
<div class="box" v-for="channel in otherPayChannels" :key="channel.code">
|
||||
<img :src="icons[channel.code]">
|
||||
<div class="title">{{ channel.name }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
<!-- 展示形式:二维码 URL -->
|
||||
<el-dialog :title="qrCode.title" :visible.sync="qrCode.visible" width="350px" append-to-body
|
||||
:close-on-press-escape="false">
|
||||
<qrcode-vue :value="qrCode.url" size="310" level="L" />
|
||||
</el-dialog>
|
||||
|
||||
<!-- 展示形式:IFrame -->
|
||||
<el-dialog :title="iframe.title" :visible.sync="iframe.visible" width="800px" height="800px" append-to-body
|
||||
:close-on-press-escape="false">
|
||||
<iframe :src="iframe.url" width="100%" />
|
||||
</el-dialog>
|
||||
|
||||
<!-- 展示形式:Form -->
|
||||
<div ref="formRef" v-html="form.value" />
|
||||
|
||||
<!-- 展示形式:BarCode 条形码 -->
|
||||
<el-dialog :title="barCode.title" :visible.sync="barCode.visible" width="500px" append-to-body
|
||||
:close-on-press-escape="false">
|
||||
<el-form ref="form" label-width="80px">
|
||||
<el-row>
|
||||
<el-col :span="24">
|
||||
<el-form-item label="条形码" prop="name">
|
||||
<el-input v-model="barCode.value" placeholder="请输入条形码" required />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="24">
|
||||
<div style="text-align: right">
|
||||
或使用
|
||||
<el-link type="danger" target="_blank"
|
||||
href="https://baike.baidu.com/item/条码支付/10711903">(扫码枪/扫码盒)</el-link>
|
||||
扫码
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
<div slot="footer" class="dialog-footer">
|
||||
<el-button type="primary" @click="submit0(barCode.channelCode)"
|
||||
:disabled="barCode.value.length === 0">确认支付</el-button>
|
||||
<el-button @click="barCode.visible = false">取 消</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import QrcodeVue from 'qrcode.vue'
|
||||
import { DICT_TYPE, getDictDatas } from "@/utils/dict";
|
||||
import { getOrder, submitOrder } from '@/api/pay/order';
|
||||
import {PayChannelEnum, PayDisplayModeEnum, PayOrderStatusEnum} from "@/utils/constants";
|
||||
|
||||
export default {
|
||||
name: "PayOrderSubmit",
|
||||
components: {
|
||||
QrcodeVue,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
id: undefined, // 请假编号
|
||||
loading: false, // 支付信息的 loading
|
||||
payOrder: {}, // 支付信息
|
||||
aliPayChannels: [], // 阿里支付的渠道
|
||||
wxPayChannels: [], // 微信支付的渠道
|
||||
otherPayChannels: [], // 其它的支付渠道
|
||||
icons: {
|
||||
alipay_qr: require("@/assets/images/pay/icon/alipay_qr.svg"),
|
||||
alipay_app: require("@/assets/images/pay/icon/alipay_app.svg"),
|
||||
alipay_wap: require("@/assets/images/pay/icon/alipay_wap.svg"),
|
||||
alipay_pc: require("@/assets/images/pay/icon/alipay_pc.svg"),
|
||||
alipay_bar: require("@/assets/images/pay/icon/alipay_bar.svg"),
|
||||
wx_app: require("@/assets/images/pay/icon/wx_app.svg"),
|
||||
wx_lite: require("@/assets/images/pay/icon/wx_lite.svg"),
|
||||
wx_pub: require("@/assets/images/pay/icon/wx_pub.svg"),
|
||||
mock: require("@/assets/images/pay/icon/mock.svg"),
|
||||
},
|
||||
submitLoading: false, // 提交支付的 loading
|
||||
interval: undefined, // 定时任务,轮询是否完成支付
|
||||
qrCode: { // 展示形式:二维码
|
||||
url: '',
|
||||
title: '',
|
||||
visible: false,
|
||||
},
|
||||
iframe: { // 展示形式:iframe
|
||||
url: '',
|
||||
title: '',
|
||||
visible: false
|
||||
},
|
||||
form: { // 展示形式:form
|
||||
html: '',
|
||||
},
|
||||
barCode: { // 展示形式:条形码
|
||||
channelCode: '',
|
||||
value: '',
|
||||
title: '',
|
||||
visible: false,
|
||||
},
|
||||
};
|
||||
},
|
||||
created() {
|
||||
this.id = this.$route.query.id;
|
||||
this.getDetail();
|
||||
this.initPayChannels();
|
||||
},
|
||||
methods: {
|
||||
/** 初始化支付渠道 */
|
||||
initPayChannels() {
|
||||
// 微信支付
|
||||
for (const dict of getDictDatas(DICT_TYPE.PAY_CHANNEL_CODE_TYPE)) {
|
||||
const payChannel = {
|
||||
name: dict.label,
|
||||
code: dict.value
|
||||
}
|
||||
if (dict.value.indexOf('wx_') === 0) {
|
||||
this.wxPayChannels.push(payChannel);
|
||||
} else if (dict.value.indexOf('alipay_') === 0) {
|
||||
this.aliPayChannels.push(payChannel);
|
||||
} else {
|
||||
this.otherPayChannels.push(payChannel);
|
||||
}
|
||||
}
|
||||
},
|
||||
/** 获得支付信息 */
|
||||
getDetail() {
|
||||
// 1.1 未传递订单编号
|
||||
if (!this.id) {
|
||||
this.$message.error('未传递支付单号,无法查看对应的支付信息');
|
||||
this.goBackToList();
|
||||
return;
|
||||
}
|
||||
getOrder(this.id).then(response => {
|
||||
// 1.2 无法查询到支付信息
|
||||
if (!response.data) {
|
||||
this.$message.error('支付订单不存在,请检查!');
|
||||
this.goBackToList();
|
||||
return;
|
||||
}
|
||||
// 1.3 订单已支付
|
||||
if (response.data.status !== PayOrderStatusEnum.WAITING.status) {
|
||||
this.$message.error('支付订单不处于待支付状态,请检查!');
|
||||
this.goBackToList();
|
||||
return;
|
||||
}
|
||||
|
||||
// 2. 可以展示
|
||||
this.payOrder = response.data;
|
||||
});
|
||||
},
|
||||
/** 提交支付 */
|
||||
submit(channelCode) {
|
||||
// 条形码支付,需要特殊处理
|
||||
if (channelCode === PayChannelEnum.ALIPAY_BAR.code) {
|
||||
this.barCode = {
|
||||
channelCode: channelCode,
|
||||
value: '',
|
||||
title: '“支付宝”条码支付',
|
||||
visible: true
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// 默认的提交处理
|
||||
this.submit0(channelCode)
|
||||
},
|
||||
submit0(channelCode) {
|
||||
this.submitLoading = true
|
||||
submitOrder({
|
||||
id: this.id,
|
||||
channelCode: channelCode,
|
||||
...this.buildSubmitParam(channelCode)
|
||||
}).then(response => {
|
||||
const data = response.data
|
||||
if (data.displayMode === PayDisplayModeEnum.IFRAME.mode) {
|
||||
this.displayIFrame(channelCode, data)
|
||||
} else if (data.displayMode === PayDisplayModeEnum.URL.mode) {
|
||||
this.displayUrl(channelCode, data)
|
||||
} else if (data.displayMode === PayDisplayModeEnum.FORM.mode) {
|
||||
this.displayForm(channelCode, data)
|
||||
} else if (data.displayMode === PayDisplayModeEnum.QR_CODE.mode) {
|
||||
this.displayQrCode(channelCode, data)
|
||||
}
|
||||
|
||||
// 打开轮询任务
|
||||
this.createQueryInterval()
|
||||
}).catch(() => {
|
||||
this.submitLoading = false
|
||||
});
|
||||
},
|
||||
/** 构建提交支付的额外参数 */
|
||||
buildSubmitParam(channelCode) {
|
||||
// ① 支付宝 PC 支付时,有多种展示形态
|
||||
if (channelCode === PayChannelEnum.ALIPAY_PC.code) {
|
||||
// 情况【前置模式】:将二维码前置到商户的订单确认页的模式。需要商户在自己的页面中以 iframe 方式请求支付宝页面。具体支持的枚举值有以下几种:
|
||||
// 0:订单码-简约前置模式,对应 iframe 宽度不能小于 600px,高度不能小于 300px
|
||||
// return {
|
||||
// "channelExtras": {
|
||||
// "qr_pay_mode": "0"
|
||||
// }
|
||||
// }
|
||||
// 1:订单码-前置模式,对应iframe 宽度不能小于 300px,高度不能小于 600px
|
||||
// return {
|
||||
// "channelExtras": {
|
||||
// "qr_pay_mode": "1"
|
||||
// }
|
||||
// }
|
||||
// 3:订单码-迷你前置模式,对应 iframe 宽度不能小于 75px,高度不能小于 75px
|
||||
// return {
|
||||
// "channelExtras": {
|
||||
// "qr_pay_mode": "3"
|
||||
// }
|
||||
// }
|
||||
// 4:订单码-可定义宽度的嵌入式二维码,商户可根据需要设定二维码的大小
|
||||
// return {
|
||||
// "channelExtras": {
|
||||
// "qr_pay_mode": "4"
|
||||
// }
|
||||
// }
|
||||
// 情况【跳转模式】:跳转模式下,用户的扫码界面是由支付宝生成的,不在商户的域名下。支持传入的枚举值有
|
||||
return {
|
||||
"channelExtras": {
|
||||
"qr_pay_mode": "2"
|
||||
}
|
||||
}
|
||||
// 情况【表单模式】:直接提交当前页面到支付宝
|
||||
// return {
|
||||
// displayMode: PayDisplayModeEnum.FORM.mode
|
||||
// }
|
||||
}
|
||||
|
||||
// ② 支付宝 Wap 支付时,引导手机扫码支付
|
||||
if (channelCode === PayChannelEnum.ALIPAY_WAP.code) {
|
||||
return {
|
||||
displayMode: PayDisplayModeEnum.QR_CODE.mode
|
||||
}
|
||||
}
|
||||
|
||||
// ③ 支付宝 BarCode 支付时,需要传递 authCode 条形码
|
||||
if (channelCode === PayChannelEnum.ALIPAY_BAR.code) {
|
||||
return {
|
||||
"channelExtras": {
|
||||
"auth_code": this.barCode.value
|
||||
}
|
||||
}
|
||||
}
|
||||
return {}
|
||||
},
|
||||
/** 提交支付后,IFrame 内置 URL 的展示形式 */
|
||||
displayIFrame(channelCode, data) {
|
||||
// TODO 芋艿:目前有点奇怪,支付宝总是会显示“支付环境存在风险”
|
||||
this.iframe = {
|
||||
title: '支付窗口',
|
||||
url: data.displayContent,
|
||||
visible: true
|
||||
}
|
||||
this.submitLoading = false
|
||||
},
|
||||
/** 提交支付后,URL 的展示形式 */
|
||||
displayUrl(channelCode, data) {
|
||||
window.open(data.displayContent)
|
||||
this.submitLoading = false
|
||||
},
|
||||
/** 提交支付后,Form 的展示形式 */
|
||||
displayForm(channelCode, data) {
|
||||
// 渲染支付页面
|
||||
this.form = {
|
||||
value: data.displayContent
|
||||
}
|
||||
// 防抖避免重复支付
|
||||
this.$nextTick(() => {
|
||||
// 提交支付表单
|
||||
this.$refs.formRef.children[0].submit();
|
||||
setTimeout(() => {
|
||||
this.submitLoading = false
|
||||
}, 1000);
|
||||
});
|
||||
},
|
||||
/** 提交支付后(支付宝扫码支付) */
|
||||
displayQrCode(channelCode, data) {
|
||||
let title = '请使用手机浏览器“扫一扫”';
|
||||
if (channelCode === PayChannelEnum.ALIPAY_WAP.code) {
|
||||
// 考虑到 WAP 测试,所以引导手机浏览器搞
|
||||
} else if (channelCode.indexOf('alipay_') === 0) {
|
||||
title = '请使用支付宝“扫一扫”扫码支付';
|
||||
} else if (channelCode.indexOf('wx_') === 0) {
|
||||
title = '请使用微信“扫一扫”扫码支付';
|
||||
}
|
||||
this.qrCode = {
|
||||
title: title,
|
||||
url: data.displayContent,
|
||||
visible: true
|
||||
}
|
||||
this.submitLoading = false
|
||||
},
|
||||
/** 轮询查询任务 */
|
||||
createQueryInterval() {
|
||||
if (this.interval) {
|
||||
return
|
||||
}
|
||||
this.interval = setInterval(() => {
|
||||
getOrder(this.id).then(response => {
|
||||
// 已支付
|
||||
if (response.data.status === PayOrderStatusEnum.SUCCESS.status) {
|
||||
this.clearQueryInterval();
|
||||
this.$message.success('支付成功!');
|
||||
this.goBackToList();
|
||||
}
|
||||
// 已取消
|
||||
if (response.data.status === PayOrderStatusEnum.CLOSED.status) {
|
||||
this.clearQueryInterval();
|
||||
this.$message.error('支付已关闭!');
|
||||
this.goBackToList();
|
||||
}
|
||||
})
|
||||
}, 1000 * 2)
|
||||
},
|
||||
/** 清空查询任务 */
|
||||
clearQueryInterval() {
|
||||
// 清空各种弹窗
|
||||
this.qrCode = {
|
||||
title: '',
|
||||
url: '',
|
||||
visible: false
|
||||
}
|
||||
// 清空任务
|
||||
clearInterval(this.interval)
|
||||
this.interval = undefined
|
||||
},
|
||||
/** 回到列表 **/
|
||||
goBackToList() {
|
||||
this.$tab.closePage();
|
||||
this.$router.go(-1);
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.pay-channel-container {
|
||||
display: flex;
|
||||
margin-top: -10px;
|
||||
.box {
|
||||
width: 130px;
|
||||
border: 1px solid #e6ebf5;
|
||||
cursor: pointer;
|
||||
text-align: center;
|
||||
padding-top: 10px;
|
||||
padding-bottom: 5px;
|
||||
margin-right: 10px;
|
||||
img {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
}
|
||||
.title {
|
||||
padding-top: 5px
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|