!417 支付收银台,接入支付宝的 PC、Wap、二维码、条码、App 等支付方式

Merge pull request !417 from 芋道源码/feature/dev-yunai
This commit is contained in:
芋道源码 2023-02-22 14:43:10 +00:00 committed by Gitee
commit c419c1e107
No known key found for this signature in database
GPG Key ID: 173E9B9CA92EEF8F
97 changed files with 10468 additions and 9814 deletions

View File

@ -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);
}

View File

@ -1,7 +1,12 @@
package cn.iocoder.yudao.framework.pay.core.client; package cn.iocoder.yudao.framework.pay.core.client;
import cn.iocoder.yudao.framework.pay.core.client.dto.notify.PayNotifyReqDTO;
import cn.iocoder.yudao.framework.pay.core.client.dto.*; 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实现发起支付退款等功能 * 支付客户端用于对接各支付渠道的 SDK实现发起支付退款等功能
@ -23,51 +28,25 @@ public interface PayClient {
* @param reqDTO 下单信息 * @param reqDTO 下单信息
* @return 各支付渠道的返回结果 * @return 各支付渠道的返回结果
*/ */
PayCommonResult<?> unifiedOrder(PayOrderUnifiedReqDTO reqDTO); PayOrderUnifiedRespDTO unifiedOrder(PayOrderUnifiedReqDTO reqDTO);
/**
* 解析支付单的通知结果
*
* @param data 通知结果
* @return 解析结果
* @throws Exception 解析失败抛出异常
*/
PayOrderNotifyRespDTO parseOrderNotify(PayNotifyDataDTO data) throws Exception;
/** /**
* 调用支付渠道进行退款 * 调用支付渠道进行退款
* @param reqDTO 统一退款请求信息 * @param reqDTO 统一退款请求信息
* @return 各支付渠道的统一返回结果 * @return 各支付渠道的统一返回结果
*/ */
PayCommonResult<PayRefundUnifiedRespDTO> unifiedRefund(PayRefundUnifiedReqDTO reqDTO); PayRefundUnifiedRespDTO unifiedRefund(PayRefundUnifiedReqDTO reqDTO);
/** /**
* 解析支付退款通知数据 * 解析回调数据
* @param notifyData 支付退款通知请求数据
* @return 支付退款通知的Notify DTO
*/
PayRefundNotifyDTO parseRefundNotify(PayNotifyDataDTO notifyData);
// TODO @芋艿后续改成非 default避免不知道去实现
/**
* 验证是否渠道通知
* *
* @param notifyData 通知数据 * @param rawNotify 通知内容
* @return 默认是 true * @return 回调对象
* 1. {@link PayRefundNotifyRespDTO} 退款通知
* 2. {@link PayOrderNotifyRespDTO} 支付通知
*/ */
default boolean verifyNotifyData(PayNotifyDataDTO notifyData) { default Object parseNotify(PayNotifyReqDTO rawNotify) {
return true; throw new UnsupportedOperationException("未实现 parseNotify 方法!");
}
// TODO @芋艿后续改成非 default避免不知道去实现
/**
* 判断是否为退款通知
*
* @param notifyData 通知数据
* @return 默认是 false
*/
default boolean isRefundNotify(PayNotifyDataDTO notifyData){
return false;
} }
} }

View File

@ -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;
}
}

View File

@ -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.Builder;
import lombok.Data; import lombok.Data;
@ -13,11 +13,11 @@ import java.util.Map;
@Data @Data
@ToString @ToString
@Builder @Builder
public class PayNotifyDataDTO { public class PayNotifyReqDTO {
/** /**
* HTTP 回调接口的 request body * HTTP 回调接口的 request body
*/ */
private String body; private String body;

View File

@ -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.AllArgsConstructor;
import lombok.Builder; import lombok.Builder;
@ -35,15 +35,9 @@ public class PayOrderNotifyRespDTO {
*/ */
private LocalDateTime successTime; private LocalDateTime successTime;
/**
* 通知的原始数据
*
* 主要用于持久化方便后续修复数据或者排错
*/
private String data;
/** /**
* TODO @jason 结合其他的渠道定义成枚举, * TODO @jason 结合其他的渠道定义成枚举,
*
* alipay * alipay
* TRADE_CLOSED,未付款交易超时关闭或支付完成后全额退款 * TRADE_CLOSED,未付款交易超时关闭或支付完成后全额退款
* TRADE_SUCCESS, 交易支付成功 * TRADE_SUCCESS, 交易支付成功

View File

@ -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 cn.iocoder.yudao.framework.pay.core.enums.PayNotifyRefundStatusEnum;
import lombok.Builder; import lombok.Builder;
@ -15,14 +15,13 @@ import java.time.LocalDateTime;
@Data @Data
@ToString @ToString
@Builder @Builder
public class PayRefundNotifyDTO { public class PayRefundNotifyRespDTO {
/** /**
* 支付渠道编号 * 支付渠道编号
*/ */
private String channelOrderNo; private String channelOrderNo;
/** /**
* 交易订单号根据规则生成 * 交易订单号根据规则生成
* 调用支付渠道时使用该字段作为对接的订单号 * 调用支付渠道时使用该字段作为对接的订单号
@ -46,18 +45,14 @@ public class PayRefundNotifyDTO {
*/ */
private String reqNo; private String reqNo;
/** /**
* 退款是否成功 * 退款是否成功
*/ */
private PayNotifyRefundStatusEnum status; private PayNotifyRefundStatusEnum status;
/** /**
* 退款成功时间 * 退款成功时间
*/ */
private LocalDateTime refundSuccessTime; private LocalDateTime refundSuccessTime;
} }

View File

@ -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 lombok.Data;
import org.hibernate.validator.constraints.Length; import org.hibernate.validator.constraints.Length;
import org.hibernate.validator.constraints.URL; 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.DecimalMin;
import javax.validation.constraints.NotEmpty; import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull; import javax.validation.constraints.NotNull;
import java.awt.*;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.util.Map; import java.util.Map;
@ -78,4 +80,13 @@ public class PayOrderUnifiedReqDTO {
*/ */
private Map<String, String> channelExtras; private Map<String, String> channelExtras;
/**
* 展示模式
*
* 如果不传递则每个支付渠道使用默认的方式
*
* 枚举 {@link PayDisplayModeEnum}
*/
private String displayMode;
} }

View File

@ -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;
}

View File

@ -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.AllArgsConstructor;
import lombok.Builder; import lombok.Builder;

View File

@ -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 cn.iocoder.yudao.framework.pay.core.enums.PayChannelRefundRespEnum;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;

View File

@ -1,17 +1,23 @@
package cn.iocoder.yudao.framework.pay.core.client.impl; 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.PayClient;
import cn.iocoder.yudao.framework.pay.core.client.PayClientConfig; 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.order.PayOrderUnifiedReqDTO;
import cn.iocoder.yudao.framework.pay.core.client.dto.PayOrderUnifiedReqDTO; import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderUnifiedRespDTO;
import cn.iocoder.yudao.framework.pay.core.client.dto.PayRefundUnifiedReqDTO; import cn.iocoder.yudao.framework.pay.core.client.dto.refund.PayRefundUnifiedReqDTO;
import cn.iocoder.yudao.framework.pay.core.client.dto.PayRefundUnifiedRespDTO; import cn.iocoder.yudao.framework.pay.core.client.dto.refund.PayRefundUnifiedRespDTO;
import lombok.extern.slf4j.Slf4j; import com.alipay.api.AlipayResponse;import lombok.extern.slf4j.Slf4j;
import javax.validation.Validation; 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.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; private final String channelCode;
/**
* 错误码枚举类
*/
protected AbstractPayCodeMapping codeMapping;
/** /**
* 支付配置 * 支付配置
*/ */
protected Config config; 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.channelId = channelId;
this.channelCode = channelCode; this.channelCode = channelCode;
this.codeMapping = codeMapping;
this.config = config; this.config = config;
} }
@ -69,47 +70,72 @@ public abstract class AbstractPayClient<Config extends PayClientConfig> implemen
this.init(); this.init();
} }
protected Double calculateAmount(Integer amount) {
return amount / 100.0;
}
@Override @Override
public Long getId() { public Long getId() {
return channelId; return channelId;
} }
@Override @Override
public final PayCommonResult<?> unifiedOrder(PayOrderUnifiedReqDTO reqDTO) { public final PayOrderUnifiedRespDTO unifiedOrder(PayOrderUnifiedReqDTO reqDTO) {
Validation.buildDefaultValidatorFactory().getValidator().validate(reqDTO); Validation.buildDefaultValidatorFactory().getValidator().validate(reqDTO);
// 执行短信发送 // 执行短信发送
PayCommonResult<?> result; PayOrderUnifiedRespDTO result;
try { try {
result = doUnifiedOrder(reqDTO); result = doUnifiedOrder(reqDTO);
} catch (Throwable ex) { } catch (Throwable ex) {
// 打印异常日志 // 打印异常日志
log.error("[unifiedOrder][request({}) 发起支付失败]", toJsonString(reqDTO), ex); log.error("[unifiedOrder][request({}) 发起支付失败]", toJsonString(reqDTO), ex);
// 封装返回 throw buildException(ex);
return PayCommonResult.error(ex);
} }
return result; return result;
} }
protected abstract PayCommonResult<?> doUnifiedOrder(PayOrderUnifiedReqDTO reqDTO) protected abstract PayOrderUnifiedRespDTO doUnifiedOrder(PayOrderUnifiedReqDTO reqDTO)
throws Throwable; throws Throwable;
@Override @Override
public PayCommonResult<PayRefundUnifiedRespDTO> unifiedRefund(PayRefundUnifiedReqDTO reqDTO) { public PayRefundUnifiedRespDTO unifiedRefund(PayRefundUnifiedReqDTO reqDTO) {
PayCommonResult<PayRefundUnifiedRespDTO> resp; PayRefundUnifiedRespDTO resp;
try { try {
resp = doUnifiedRefund(reqDTO); resp = doUnifiedRefund(reqDTO);
} catch (Throwable ex) { } catch (Throwable ex) {
// 记录异常日志 // 记录异常日志
log.error("[unifiedRefund][request({}) 发起退款失败]", toJsonString(reqDTO), ex); log.error("[unifiedRefund][request({}) 发起退款失败]", toJsonString(reqDTO), ex);
resp = PayCommonResult.error(ex); throw buildException(ex);
} }
return resp; 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);
}
} }

View File

@ -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.PayClient;
import cn.iocoder.yudao.framework.pay.core.client.PayClientConfig; 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.PayClientFactory;
import cn.iocoder.yudao.framework.pay.core.client.impl.alipay.AlipayPayClientConfig; import cn.iocoder.yudao.framework.pay.core.client.impl.alipay.*;
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.wx.WXLitePayClient; 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.WXNativePayClient;
import cn.iocoder.yudao.framework.pay.core.client.impl.wx.WXPayClientConfig; 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 WX_NATIVE: return (AbstractPayClient<Config>) new WXNativePayClient(channelId, (WXPayClientConfig) config);
case ALIPAY_WAP: return (AbstractPayClient<Config>) new AlipayWapPayClient(channelId, (AlipayPayClientConfig) 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_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_PC: return (AbstractPayClient<Config>) new AlipayPcPayClient(channelId, (AlipayPayClientConfig) config);
case ALIPAY_BAR: return (AbstractPayClient<Config>) new AlipayBarPayClient(channelId, (AlipayPayClientConfig) config);
} }
// 创建失败错误日志 + 抛出异常 // 创建失败错误日志 + 抛出异常
log.error("[createPayClient][配置({}) 找不到合适的客户端实现]", config); log.error("[createPayClient][配置({}) 找不到合适的客户端实现]", config);

View File

@ -1,10 +1,12 @@
package cn.iocoder.yudao.framework.pay.core.client.impl.alipay; package cn.iocoder.yudao.framework.pay.core.client.impl.alipay;
import cn.hutool.core.bean.BeanUtil; import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.date.LocalDateTimeUtil; import cn.hutool.http.HttpUtil;
import cn.iocoder.yudao.framework.pay.core.client.AbstractPayCodeMapping; import cn.iocoder.yudao.framework.pay.core.client.dto.notify.PayNotifyReqDTO;
import cn.iocoder.yudao.framework.pay.core.client.PayCommonResult; import cn.iocoder.yudao.framework.pay.core.client.dto.notify.PayOrderNotifyRespDTO;
import cn.iocoder.yudao.framework.pay.core.client.dto.*; 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.client.impl.AbstractPayClient;
import cn.iocoder.yudao.framework.pay.core.enums.PayNotifyRefundStatusEnum; import cn.iocoder.yudao.framework.pay.core.enums.PayNotifyRefundStatusEnum;
import com.alipay.api.AlipayApiException; import com.alipay.api.AlipayApiException;
@ -18,7 +20,6 @@ import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map; import java.util.Map;
import static cn.iocoder.yudao.framework.common.util.json.JsonUtils.toJsonString; 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; protected DefaultAlipayClient client;
public AbstractAlipayClient(Long channelId, String channelCode, public AbstractAlipayClient(Long channelId, String channelCode, AlipayPayClientConfig config) {
AlipayPayClientConfig config, AbstractPayCodeMapping codeMapping) { super(channelId, channelCode, config);
super(channelId, channelCode, config, codeMapping);
} }
@Override @Override
@ -46,71 +46,25 @@ public abstract class AbstractAlipayClient extends AbstractPayClient<AlipayPayCl
this.client = new DefaultAlipayClient(alipayConfig); 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 * 支付宝统一的退款接口 alipay.trade.refund
* @param reqDTO 退款请求 request DTO * @param reqDTO 退款请求 request DTO
* @return 退款请求 Response * @return 退款请求 Response
*/ */
@Override @Override
protected PayCommonResult<PayRefundUnifiedRespDTO> doUnifiedRefund(PayRefundUnifiedReqDTO reqDTO) { protected PayRefundUnifiedRespDTO doUnifiedRefund(PayRefundUnifiedReqDTO reqDTO) {
AlipayTradeRefundModel model=new AlipayTradeRefundModel(); AlipayTradeRefundModel model=new AlipayTradeRefundModel();
model.setTradeNo(reqDTO.getChannelOrderNo()); model.setTradeNo(reqDTO.getChannelOrderNo());
model.setOutTradeNo(reqDTO.getPayTradeNo()); model.setOutTradeNo(reqDTO.getPayTradeNo());
model.setOutRequestNo(reqDTO.getMerchantRefundId()); model.setOutRequestNo(reqDTO.getMerchantRefundId());
model.setRefundAmount(calculateAmount(reqDTO.getAmount()).toString()); model.setRefundAmount(formatAmount(reqDTO.getAmount()).toString());
model.setRefundReason(reqDTO.getReason()); model.setRefundReason(reqDTO.getReason());
AlipayTradeRefundRequest refundRequest = new AlipayTradeRefundRequest(); AlipayTradeRefundRequest refundRequest = new AlipayTradeRefundRequest();
refundRequest.setBizModel(model); refundRequest.setBizModel(model);
refundRequest.setNotifyUrl(reqDTO.getNotifyUrl());
refundRequest.setReturnUrl(reqDTO.getNotifyUrl());
try { try {
AlipayTradeRefundResponse response = client.execute(refundRequest); AlipayTradeRefundResponse response = client.execute(refundRequest);
log.info("[doUnifiedRefund][response({}) 发起退款 渠道返回", toJsonString(response)); log.info("[doUnifiedRefund][response({}) 发起退款 渠道返回", toJsonString(response));
@ -119,38 +73,43 @@ public abstract class AbstractAlipayClient extends AbstractPayClient<AlipayPayCl
//支付宝不返回退款单号设置为空 //支付宝不返回退款单号设置为空
PayRefundUnifiedRespDTO respDTO = new PayRefundUnifiedRespDTO(); PayRefundUnifiedRespDTO respDTO = new PayRefundUnifiedRespDTO();
respDTO.setChannelRefundId(""); 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) { } catch (AlipayApiException e) {
// TODO 记录异常日志 // TODO 记录异常日志
log.error("[doUnifiedRefund][request({}) 发起退款失败,网络读超时,退款状态未知]", toJsonString(reqDTO), e); 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");
// 2.1 退款的情况
/** if (bodyObj.containsKey("refund_fee")) {
* 支付宝统一回调参数 str map return PayRefundNotifyRespDTO.builder().channelOrderNo(bodyObj.get("trade_no"))
* .tradeNo(bodyObj.get("out_trade_no")).reqNo(bodyObj.get("out_biz_no"))
* @param s 支付宝支付通知回调参数 .status(PayNotifyRefundStatusEnum.SUCCESS)
* @return map 支付宝集合 .refundSuccessTime(parseTime(params.get("gmt_refund")))
*/ .build();
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]);
} }
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();
} }
} }

View File

@ -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("");
}
}

View File

@ -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("");
}
}

View File

@ -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;
}
}

View File

@ -1,21 +1,24 @@
package cn.iocoder.yudao.framework.pay.core.client.impl.alipay; package cn.iocoder.yudao.framework.pay.core.client.impl.alipay;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.StrUtil; import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.common.util.json.JsonUtils; import cn.hutool.http.Method;
import cn.iocoder.yudao.framework.pay.core.client.PayCommonResult; import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderUnifiedReqDTO;
import cn.iocoder.yudao.framework.pay.core.client.dto.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.PayChannelEnum;
import com.alibaba.fastjson.JSONObject; import cn.iocoder.yudao.framework.pay.core.enums.PayDisplayModeEnum;
import com.alipay.api.AlipayApiException; import com.alipay.api.AlipayApiException;
import com.alipay.api.domain.AlipayTradePagePayModel; import com.alipay.api.domain.AlipayTradePagePayModel;
import com.alipay.api.request.AlipayTradePagePayRequest; import com.alipay.api.request.AlipayTradePagePayRequest;
import com.alipay.api.response.AlipayTradePagePayResponse; import com.alipay.api.response.AlipayTradePagePayResponse;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import java.util.Objects;
/** /**
* 支付宝PC网站支付 PayClient 实现类 * 支付宝PC 网站 PayClient 实现类
* 文档https://opendocs.alipay.com/open/270/105898 *
* 文档<a href="https://opendocs.alipay.com/open/270/105898">电脑网站支付</a>
* *
* @author XGD * @author XGD
*/ */
@ -23,38 +26,66 @@ import lombok.extern.slf4j.Slf4j;
public class AlipayPcPayClient extends AbstractAlipayClient { public class AlipayPcPayClient extends AbstractAlipayClient {
public AlipayPcPayClient(Long channelId, AlipayPayClientConfig config) { public AlipayPcPayClient(Long channelId, AlipayPayClientConfig config) {
super(channelId, PayChannelEnum.ALIPAY_PC.getCode(), config, new AlipayPayCodeMapping()); super(channelId, PayChannelEnum.ALIPAY_PC.getCode(), config);
} }
@Override @Override
public PayCommonResult<AlipayTradePagePayResponse> doUnifiedOrder(PayOrderUnifiedReqDTO reqDTO) { public PayOrderUnifiedRespDTO doUnifiedOrder(PayOrderUnifiedReqDTO reqDTO) throws AlipayApiException {
// 构建 AlipayTradePagePayModel 请求 // 1.1 构建 AlipayTradePagePayModel 请求
AlipayTradePagePayModel model = new 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(); AlipayTradePagePayRequest request = new AlipayTradePagePayRequest();
request.setBizModel(model); 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.setNotifyUrl(reqDTO.getNotifyUrl());
request.setReturnUrl(""); request.setReturnUrl(reqDTO.getReturnUrl());
// 执行请求
// 2.1 执行请求
AlipayTradePagePayResponse response; AlipayTradePagePayResponse response;
try { if (Objects.equals(displayMode, PayDisplayModeEnum.FORM.getMode())) {
response = client.pageExecute(request); response = client.pageExecute(request, Method.POST.name()); // 需要特殊使用 POST 请求
} catch (AlipayApiException e) { } else {
log.error("[unifiedOrder][request({}) 发起支付失败]", JsonUtils.toJsonString(reqDTO), e); response = client.pageExecute(request, Method.GET.name());
return PayCommonResult.build(e.getErrCode(), e.getErrMsg(), null, codeMapping);
} }
// 响应为表单格式前端可嵌入响应的页面或关闭当前支付窗口
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 跳转
}
} }

View File

@ -1,19 +1,19 @@
package cn.iocoder.yudao.framework.pay.core.client.impl.alipay; 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.order.PayOrderUnifiedReqDTO;
import cn.iocoder.yudao.framework.pay.core.client.dto.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.PayChannelEnum;
import cn.iocoder.yudao.framework.pay.core.enums.PayDisplayModeEnum;
import com.alipay.api.AlipayApiException; import com.alipay.api.AlipayApiException;
import com.alipay.api.domain.AlipayTradePrecreateModel; import com.alipay.api.domain.AlipayTradePrecreateModel;
import com.alipay.api.request.AlipayTradePrecreateRequest; import com.alipay.api.request.AlipayTradePrecreateRequest;
import com.alipay.api.response.AlipayTradePrecreateResponse; import com.alipay.api.response.AlipayTradePrecreateResponse;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import static cn.iocoder.yudao.framework.common.util.json.JsonUtils.toJsonString;
/** /**
* 支付宝扫码支付 PayClient 实现类 * 支付宝扫码支付 PayClient 实现类
* 文档https://opendocs.alipay.com/apis/02890k *
* 文档<a href="https://opendocs.alipay.com/apis/02890k">扫码支付</a>
* *
* @author 芋道源码 * @author 芋道源码
*/ */
@ -21,32 +21,34 @@ import static cn.iocoder.yudao.framework.common.util.json.JsonUtils.toJsonString
public class AlipayQrPayClient extends AbstractAlipayClient { public class AlipayQrPayClient extends AbstractAlipayClient {
public AlipayQrPayClient(Long channelId, AlipayPayClientConfig config) { public AlipayQrPayClient(Long channelId, AlipayPayClientConfig config) {
super(channelId, PayChannelEnum.ALIPAY_QR.getCode(), config, new AlipayPayCodeMapping()); super(channelId, PayChannelEnum.ALIPAY_QR.getCode(), config);
} }
@Override @Override
public PayCommonResult<AlipayTradePrecreateResponse> doUnifiedOrder(PayOrderUnifiedReqDTO reqDTO) { public PayOrderUnifiedRespDTO doUnifiedOrder(PayOrderUnifiedReqDTO reqDTO) throws AlipayApiException {
// 构建 AlipayTradePrecreateModel 请求 // 1.1 构建 AlipayTradePrecreateModel 请求
AlipayTradePrecreateModel model = new AlipayTradePrecreateModel(); AlipayTradePrecreateModel model = new AlipayTradePrecreateModel();
// 通用的参数
model.setOutTradeNo(reqDTO.getMerchantOrderId()); model.setOutTradeNo(reqDTO.getMerchantOrderId());
model.setSubject(reqDTO.getSubject()); model.setSubject(reqDTO.getSubject());
model.setBody(reqDTO.getBody()); model.setBody(reqDTO.getBody());
model.setTotalAmount(calculateAmount(reqDTO.getAmount()).toString()); // 单位 model.setTotalAmount(formatAmount(reqDTO.getAmount()));
// TODO 芋艿userIp + expireTime model.setProductCode("FACE_TO_FACE_PAYMENT"); // 销售产品码. 目前扫码支付场景下仅支持 FACE_TO_FACE_PAYMENT
// 构建 AlipayTradePrecreateRequest // 个性化的参数
// 支付宝扫码支付只有一种展示考虑到前端可能希望二维码扫描后手机打开
String displayMode = PayDisplayModeEnum.QR_CODE.getMode();
// 1.2 构建 AlipayTradePrecreateRequest 请求
AlipayTradePrecreateRequest request = new AlipayTradePrecreateRequest(); AlipayTradePrecreateRequest request = new AlipayTradePrecreateRequest();
request.setBizModel(model); request.setBizModel(model);
request.setNotifyUrl(reqDTO.getNotifyUrl()); request.setNotifyUrl(reqDTO.getNotifyUrl());
request.setReturnUrl(reqDTO.getReturnUrl()); request.setReturnUrl(reqDTO.getReturnUrl());
// 执行请求
AlipayTradePrecreateResponse response; // 2.1 执行请求
try { AlipayTradePrecreateResponse response = client.execute(request);
response = client.execute(request); // 2.2 处理结果
} catch (AlipayApiException e) { validateSuccess(response);
log.error("[unifiedOrder][request({}) 发起支付失败]", toJsonString(reqDTO), e); return new PayOrderUnifiedRespDTO()
return PayCommonResult.build(e.getErrCode(), e.getErrMsg(), null, codeMapping); .setDisplayMode(displayMode).setDisplayContent(response.getQrCode());
}
// TODO 芋艿sub Code 需要测试下各种失败的情况
return PayCommonResult.build(response.getCode(), response.getMsg(), response, codeMapping);
} }
} }

View File

@ -1,75 +1,60 @@
package cn.iocoder.yudao.framework.pay.core.client.impl.alipay; package cn.iocoder.yudao.framework.pay.core.client.impl.alipay;
import cn.hutool.core.date.DateUtil; import cn.hutool.core.util.ObjectUtil;
import cn.iocoder.yudao.framework.pay.core.client.PayCommonResult; import cn.hutool.http.Method;
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.PayChannelEnum;
import cn.iocoder.yudao.framework.pay.core.enums.PayDisplayModeEnum;
import com.alipay.api.AlipayApiException; import com.alipay.api.AlipayApiException;
import com.alipay.api.domain.AlipayTradeWapPayModel; import com.alipay.api.domain.AlipayTradeWapPayModel;
import com.alipay.api.request.AlipayTradeWapPayRequest; import com.alipay.api.request.AlipayTradeWapPayRequest;
import com.alipay.api.response.AlipayTradeWapPayResponse; import com.alipay.api.response.AlipayTradeWapPayResponse;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import java.util.Objects;
/** /**
* 支付宝手机网站 PayClient 实现类 * 支付宝Wap 网站 PayClient 实现类
* 文档https://opendocs.alipay.com/apis/api_1/alipay.trade.wap.pay *
* 文档<a href="https://opendocs.alipay.com/apis/api_1/alipay.trade.wap.pay">手机网站支付接口</a>
* *
* @author 芋道源码 * @author 芋道源码
*/ */
@Slf4j @Slf4j
public class AlipayWapPayClient extends AbstractAlipayClient { public class AlipayWapPayClient extends AbstractAlipayClient {
public AlipayWapPayClient(Long channelId, AlipayPayClientConfig config) { public AlipayWapPayClient(Long channelId, AlipayPayClientConfig config) {
super(channelId, PayChannelEnum.ALIPAY_WAP.getCode(), config, new AlipayPayCodeMapping()); super(channelId, PayChannelEnum.ALIPAY_WAP.getCode(), config);
} }
@Override @Override
public PayCommonResult<AlipayTradeWapPayResponse> doUnifiedOrder(PayOrderUnifiedReqDTO reqDTO) { public PayOrderUnifiedRespDTO doUnifiedOrder(PayOrderUnifiedReqDTO reqDTO) throws AlipayApiException {
// 构建 AlipayTradeWapPayModel 请求 // 1.1 构建 AlipayTradeWapPayModel 请求
AlipayTradeWapPayModel model = new AlipayTradeWapPayModel(); AlipayTradeWapPayModel model = new AlipayTradeWapPayModel();
// 通用的参数
model.setOutTradeNo(reqDTO.getMerchantOrderId()); model.setOutTradeNo(reqDTO.getMerchantOrderId());
model.setSubject(reqDTO.getSubject()); model.setSubject(reqDTO.getSubject());
model.setBody(reqDTO.getBody()); model.setBody(reqDTO.getBody());
model.setTotalAmount(calculateAmount(reqDTO.getAmount()).toString()); model.setTotalAmount(formatAmount(reqDTO.getAmount()));
model.setProductCode("QUICK_WAP_PAY"); // TODO 芋艿这里咋整 model.setProductCode("QUICK_WAP_PAY"); // 销售产品码. 目前 Wap 支付场景下仅支持 QUICK_WAP_PAY
//TODO 芋艿这里咋整 jason @芋艿 可以去掉吧, // 个性化的参数
// TODO 芋艿 似乎这里不用传sellerId // 支付宝 Wap 支付只有一种展示考虑到前端可能希望二维码扫描后手机打开
// https://opendocs.alipay.com/apis/api_1/alipay.trade.wap.pay String displayMode = ObjectUtil.defaultIfNull(reqDTO.getDisplayMode(),
//model.setSellerId("2088102147948060"); PayDisplayModeEnum.URL.getMode());
model.setTimeExpire(DateUtil.format(reqDTO.getExpireTime(),"yyyy-MM-dd HH:mm:ss"));
// TODO 芋艿userIp // 1.2 构建 AlipayTradeWapPayRequest 请求
// 构建 AlipayTradeWapPayRequest
AlipayTradeWapPayRequest request = new AlipayTradeWapPayRequest(); AlipayTradeWapPayRequest request = new AlipayTradeWapPayRequest();
request.setBizModel(model); request.setBizModel(model);
request.setNotifyUrl(reqDTO.getNotifyUrl()); request.setNotifyUrl(reqDTO.getNotifyUrl());
request.setReturnUrl(reqDTO.getReturnUrl()); request.setReturnUrl(reqDTO.getReturnUrl());
model.setQuitUrl(reqDTO.getReturnUrl());
// 执行请求 // 2.1 执行请求
AlipayTradeWapPayResponse response; AlipayTradeWapPayResponse response = client.pageExecute(request, Method.GET.name());
try {
response = client.pageExecute(request);
} catch (AlipayApiException e) {
return PayCommonResult.build(e.getErrCode(), e.getErrMsg(), null, codeMapping);
}
// TODO 芋艿sub Code // 2.2 处理结果
if(response.isSuccess() && Objects.isNull(response.getCode()) && Objects.nonNull(response.getBody())){ validateSuccess(response);
//成功alipay wap 成功 code null , body 为form 表单 return new PayOrderUnifiedRespDTO()
return PayCommonResult.build("-9999", "Success", response, codeMapping); .setDisplayMode(displayMode).setDisplayContent(response.getBody());
}else {
return PayCommonResult.build(response.getCode(), response.getMsg(), response, codeMapping);
}
} }
} }

View File

@ -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;
}
}

View File

@ -7,9 +7,13 @@ import cn.hutool.core.lang.Assert;
import cn.hutool.core.map.MapUtil; import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.StrUtil; import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.common.util.io.FileUtils; 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.dto.notify.PayNotifyReqDTO;
import cn.iocoder.yudao.framework.pay.core.client.PayCommonResult; import cn.iocoder.yudao.framework.pay.core.client.dto.notify.PayOrderNotifyRespDTO;
import cn.iocoder.yudao.framework.pay.core.client.dto.*; 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.client.impl.AbstractPayClient;
import cn.iocoder.yudao.framework.pay.core.enums.PayChannelEnum; import cn.iocoder.yudao.framework.pay.core.enums.PayChannelEnum;
import com.github.binarywang.wxpay.bean.notify.WxPayOrderNotifyResult; import com.github.binarywang.wxpay.bean.notify.WxPayOrderNotifyResult;
@ -30,11 +34,6 @@ import java.time.ZoneId;
import java.util.Date; import java.util.Date;
import java.util.Objects; 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; private WxPayService client;
public WXLitePayClient(Long channelId, WXPayClientConfig config) { public WXLitePayClient(Long channelId, WXPayClientConfig config) {
super(channelId, PayChannelEnum.WX_LITE.getCode(), config, new WXCodeMapping()); super(channelId, PayChannelEnum.WX_LITE.getCode(), config);
} }
@Override @Override
@ -71,28 +70,29 @@ public class WXLitePayClient extends AbstractPayClient<WXPayClientConfig> {
} }
@Override @Override
public PayCommonResult<WxPayMpOrderResult> doUnifiedOrder(PayOrderUnifiedReqDTO reqDTO) { public PayOrderUnifiedRespDTO doUnifiedOrder(PayOrderUnifiedReqDTO reqDTO) {
WxPayMpOrderResult response; throw new UnsupportedOperationException();
try { // WxPayMpOrderResult response;
switch (config.getApiVersion()) { // try {
case WXPayClientConfig.API_VERSION_V2: // switch (config.getApiVersion()) {
response = this.unifiedOrderV2(reqDTO); // case WXPayClientConfig.API_VERSION_V2:
break; // response = this.unifiedOrderV2(reqDTO);
case WXPayClientConfig.API_VERSION_V3: // break;
WxPayUnifiedOrderV3Result.JsapiResult responseV3 = this.unifiedOrderV3(reqDTO); // case WXPayClientConfig.API_VERSION_V3:
// V3 的结果统一转换成 V2返回的字段是一致的 // WxPayUnifiedOrderV3Result.JsapiResult responseV3 = this.unifiedOrderV3(reqDTO);
response = new WxPayMpOrderResult(); // // V3 的结果统一转换成 V2返回的字段是一致的
BeanUtil.copyProperties(responseV3, response, true); // response = new WxPayMpOrderResult();
break; // BeanUtil.copyProperties(responseV3, response, true);
default: // break;
throw new IllegalArgumentException(String.format("未知的 API 版本(%s)", config.getApiVersion())); // default:
} // throw new IllegalArgumentException(String.format("未知的 API 版本(%s)", config.getApiVersion()));
} catch (WxPayException e) { // }
log.error("[unifiedOrder][request({}) 发起支付失败,原因({})]", toJsonString(reqDTO), e); // } catch (WxPayException e) {
return PayCommonResult.build(ObjectUtils.defaultIfNull(e.getErrCode(), e.getReturnCode(), "CustomErrorCode"), // log.error("[unifiedOrder][request({}) 发起支付失败,原因({})]", toJsonString(reqDTO), e);
ObjectUtils.defaultIfNull(e.getErrCodeDes(), e.getCustomErrorMsg()), null, codeMapping); // 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); // }
// return PayCommonResult.build(CODE_SUCCESS, MESSAGE_SUCCESS, response, codeMapping);
} }
private WxPayMpOrderResult unifiedOrderV2(PayOrderUnifiedReqDTO reqDTO) throws WxPayException { private WxPayMpOrderResult unifiedOrderV2(PayOrderUnifiedReqDTO reqDTO) throws WxPayException {
@ -145,8 +145,8 @@ public class WXLitePayClient extends AbstractPayClient<WXPayClientConfig> {
* @return 支付回调对象 * @return 支付回调对象
* @throws WxPayException 微信异常类 * @throws WxPayException 微信异常类
*/ */
@Override // @Override
public PayOrderNotifyRespDTO parseOrderNotify(PayNotifyDataDTO data) throws WxPayException { public PayOrderNotifyRespDTO parseOrderNotify(PayNotifyReqDTO data) throws WxPayException {
log.info("[parseOrderNotify][微信支付回调data数据:{}]", data.getBody()); log.info("[parseOrderNotify][微信支付回调data数据:{}]", data.getBody());
// 微信支付 v2 回调结果处理 // 微信支付 v2 回调结果处理
switch (config.getApiVersion()) { 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 wxPayOrderNotifyV3Result = client.parseOrderNotifyV3Result(data.getBody(), null);
WxPayOrderNotifyV3Result.DecryptNotifyResult result = wxPayOrderNotifyV3Result.getResult(); WxPayOrderNotifyV3Result.DecryptNotifyResult result = wxPayOrderNotifyV3Result.getResult();
// 转换结果 // 转换结果
@ -172,11 +172,10 @@ public class WXLitePayClient extends AbstractPayClient<WXPayClientConfig> {
.channelOrderNo(result.getTransactionId()) .channelOrderNo(result.getTransactionId())
.channelUserId(result.getPayer().getOpenid()) .channelUserId(result.getPayer().getOpenid())
.successTime(LocalDateTimeUtil.parse(result.getSuccessTime(), "yyyy-MM-dd'T'HH:mm:ssXXX")) .successTime(LocalDateTimeUtil.parse(result.getSuccessTime(), "yyyy-MM-dd'T'HH:mm:ssXXX"))
.data(data.getBody())
.build(); .build();
} }
private PayOrderNotifyRespDTO parseOrderNotifyV2(PayNotifyDataDTO data) throws WxPayException { private PayOrderNotifyRespDTO parseOrderNotifyV2(PayNotifyReqDTO data) throws WxPayException {
WxPayOrderNotifyResult notifyResult = client.parseOrderNotifyResult(data.getBody()); WxPayOrderNotifyResult notifyResult = client.parseOrderNotifyResult(data.getBody());
Assert.isTrue(Objects.equals(notifyResult.getResultCode(), "SUCCESS"), "支付结果非 SUCCESS"); Assert.isTrue(Objects.equals(notifyResult.getResultCode(), "SUCCESS"), "支付结果非 SUCCESS");
// 转换结果 // 转换结果
@ -186,20 +185,12 @@ public class WXLitePayClient extends AbstractPayClient<WXPayClientConfig> {
.channelOrderNo(notifyResult.getTransactionId()) .channelOrderNo(notifyResult.getTransactionId())
.channelUserId(notifyResult.getOpenid()) .channelUserId(notifyResult.getOpenid())
.successTime(LocalDateTimeUtil.parse(notifyResult.getTimeEnd(), "yyyyMMddHHmmss")) .successTime(LocalDateTimeUtil.parse(notifyResult.getTimeEnd(), "yyyyMMddHHmmss"))
.data(data.getBody())
.build(); .build();
} }
@Override @Override
public PayRefundNotifyDTO parseRefundNotify(PayNotifyDataDTO notifyData) { protected PayRefundUnifiedRespDTO doUnifiedRefund(PayRefundUnifiedReqDTO reqDTO) {
//TODO 需要实现
throw new UnsupportedOperationException("需要实现");
}
@Override
protected PayCommonResult<PayRefundUnifiedRespDTO> doUnifiedRefund(PayRefundUnifiedReqDTO reqDTO) throws Throwable {
//TODO 需要实现 //TODO 需要实现
throw new UnsupportedOperationException(); throw new UnsupportedOperationException();
} }

View File

@ -6,9 +6,13 @@ import cn.hutool.core.date.LocalDateTimeUtil;
import cn.hutool.core.lang.Assert; import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.StrUtil; import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.common.util.io.FileUtils; 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.dto.notify.PayNotifyReqDTO;
import cn.iocoder.yudao.framework.pay.core.client.PayCommonResult; import cn.iocoder.yudao.framework.pay.core.client.dto.notify.PayOrderNotifyRespDTO;
import cn.iocoder.yudao.framework.pay.core.client.dto.*; 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.client.impl.AbstractPayClient;
import cn.iocoder.yudao.framework.pay.core.enums.PayChannelEnum; import cn.iocoder.yudao.framework.pay.core.enums.PayChannelEnum;
import com.github.binarywang.wxpay.bean.notify.WxPayOrderNotifyResult; import com.github.binarywang.wxpay.bean.notify.WxPayOrderNotifyResult;
@ -28,10 +32,6 @@ import java.time.ZoneId;
import java.util.Date; import java.util.Date;
import java.util.Objects; 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 支付 * 微信 App 支付
* *
@ -43,7 +43,7 @@ public class WXNativePayClient extends AbstractPayClient<WXPayClientConfig> {
private WxPayService client; private WxPayService client;
public WXNativePayClient(Long channelId, WXPayClientConfig config) { public WXNativePayClient(Long channelId, WXPayClientConfig config) {
super(channelId, PayChannelEnum.WX_NATIVE.getCode(), config, new WXCodeMapping()); super(channelId, PayChannelEnum.WX_NATIVE.getCode(), config);
} }
@Override @Override
@ -68,27 +68,28 @@ public class WXNativePayClient extends AbstractPayClient<WXPayClientConfig> {
} }
@Override @Override
public PayCommonResult<String> doUnifiedOrder(PayOrderUnifiedReqDTO reqDTO) { public PayOrderUnifiedRespDTO doUnifiedOrder(PayOrderUnifiedReqDTO reqDTO) {
// 这里原生的返回的是支付的 url 所以直接使用string接收 throw new UnsupportedOperationException();
// "invokeResponse": "weixin://wxpay/bizpayurl?pr=EGYAem7zz" // // 这里原生的返回的是支付的 url 所以直接使用string接收
String responseV3; // // "invokeResponse": "weixin://wxpay/bizpayurl?pr=EGYAem7zz"
try { // String responseV3;
switch (config.getApiVersion()) { // try {
case WXPayClientConfig.API_VERSION_V2: // switch (config.getApiVersion()) {
responseV3 = unifiedOrderV2(reqDTO).getCodeUrl(); // case WXPayClientConfig.API_VERSION_V2:
break; // responseV3 = unifiedOrderV2(reqDTO).getCodeUrl();
case WXPayClientConfig.API_VERSION_V3: // break;
responseV3 = this.unifiedOrderV3(reqDTO); // case WXPayClientConfig.API_VERSION_V3:
break; // responseV3 = this.unifiedOrderV3(reqDTO);
default: // break;
throw new IllegalArgumentException(String.format("未知的 API 版本(%s)", config.getApiVersion())); // default:
} // throw new IllegalArgumentException(String.format("未知的 API 版本(%s)", config.getApiVersion()));
} catch (WxPayException e) { // }
log.error("[unifiedOrder][request({}) 发起支付失败,原因({})]", toJsonString(reqDTO), e); // } catch (WxPayException e) {
return PayCommonResult.build(ObjectUtils.defaultIfNull(e.getErrCode(), e.getReturnCode(), "CustomErrorCode"), // log.error("[unifiedOrder][request({}) 发起支付失败,原因({})]", toJsonString(reqDTO), e);
ObjectUtils.defaultIfNull(e.getErrCodeDes(), e.getCustomErrorMsg()), null, codeMapping); // 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); // }
// return PayCommonResult.build(CODE_SUCCESS, MESSAGE_SUCCESS, responseV3, codeMapping);
} }
private WxPayNativeOrderResult unifiedOrderV2(PayOrderUnifiedReqDTO reqDTO) throws WxPayException { private WxPayNativeOrderResult unifiedOrderV2(PayOrderUnifiedReqDTO reqDTO) throws WxPayException {
@ -129,8 +130,8 @@ public class WXNativePayClient extends AbstractPayClient<WXPayClientConfig> {
* @return 支付回调对象 * @return 支付回调对象
* @throws WxPayException 微信异常类 * @throws WxPayException 微信异常类
*/ */
@Override // @Override
public PayOrderNotifyRespDTO parseOrderNotify(PayNotifyDataDTO data) throws WxPayException { public PayOrderNotifyRespDTO parseOrderNotify(PayNotifyReqDTO data) throws WxPayException {
log.info("微信支付回调data数据:{}", data.getBody()); log.info("微信支付回调data数据:{}", data.getBody());
// 微信支付 v2 回调结果处理 // 微信支付 v2 回调结果处理
switch (config.getApiVersion()) { 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 wxPayOrderNotifyV3Result = client.parseOrderNotifyV3Result(data.getBody(), null);
WxPayOrderNotifyV3Result.DecryptNotifyResult result = wxPayOrderNotifyV3Result.getResult(); WxPayOrderNotifyV3Result.DecryptNotifyResult result = wxPayOrderNotifyV3Result.getResult();
// 转换结果 // 转换结果
@ -154,11 +155,10 @@ public class WXNativePayClient extends AbstractPayClient<WXPayClientConfig> {
.orderExtensionNo(result.getOutTradeNo()) .orderExtensionNo(result.getOutTradeNo())
.channelOrderNo(result.getTradeState()) .channelOrderNo(result.getTradeState())
.successTime(LocalDateTimeUtil.parse(result.getSuccessTime(), "yyyy-MM-dd'T'HH:mm:ssXXX")) .successTime(LocalDateTimeUtil.parse(result.getSuccessTime(), "yyyy-MM-dd'T'HH:mm:ssXXX"))
.data(data.getBody())
.build(); .build();
} }
private PayOrderNotifyRespDTO parseOrderNotifyV2(PayNotifyDataDTO data) throws WxPayException { private PayOrderNotifyRespDTO parseOrderNotifyV2(PayNotifyReqDTO data) throws WxPayException {
WxPayOrderNotifyResult notifyResult = client.parseOrderNotifyResult(data.getBody()); WxPayOrderNotifyResult notifyResult = client.parseOrderNotifyResult(data.getBody());
Assert.isTrue(Objects.equals(notifyResult.getResultCode(), "SUCCESS"), "支付结果非 SUCCESS"); Assert.isTrue(Objects.equals(notifyResult.getResultCode(), "SUCCESS"), "支付结果非 SUCCESS");
// 转换结果 // 转换结果
@ -168,20 +168,12 @@ public class WXNativePayClient extends AbstractPayClient<WXPayClientConfig> {
.channelOrderNo(notifyResult.getTransactionId()) .channelOrderNo(notifyResult.getTransactionId())
.channelUserId(notifyResult.getOpenid()) .channelUserId(notifyResult.getOpenid())
.successTime(LocalDateTimeUtil.parse(notifyResult.getTimeEnd(), "yyyyMMddHHmmss")) .successTime(LocalDateTimeUtil.parse(notifyResult.getTimeEnd(), "yyyyMMddHHmmss"))
.data(data.getBody())
.build(); .build();
} }
@Override @Override
public PayRefundNotifyDTO parseRefundNotify(PayNotifyDataDTO notifyData) { protected PayRefundUnifiedRespDTO doUnifiedRefund(PayRefundUnifiedReqDTO reqDTO) {
// TODO 需要实现
throw new UnsupportedOperationException("需要实现");
}
@Override
protected PayCommonResult<PayRefundUnifiedRespDTO> doUnifiedRefund(PayRefundUnifiedReqDTO reqDTO) throws Throwable {
// TODO 需要实现 // TODO 需要实现
throw new UnsupportedOperationException(); throw new UnsupportedOperationException();
} }

View File

@ -7,9 +7,12 @@ import cn.hutool.core.lang.Assert;
import cn.hutool.core.map.MapUtil; import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.StrUtil; import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.common.util.io.FileUtils; 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.dto.notify.PayNotifyReqDTO;
import cn.iocoder.yudao.framework.pay.core.client.PayCommonResult; import cn.iocoder.yudao.framework.pay.core.client.dto.notify.PayOrderNotifyRespDTO;
import cn.iocoder.yudao.framework.pay.core.client.dto.*; 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.client.impl.AbstractPayClient;
import cn.iocoder.yudao.framework.pay.core.enums.PayChannelEnum; import cn.iocoder.yudao.framework.pay.core.enums.PayChannelEnum;
import com.github.binarywang.wxpay.bean.notify.WxPayOrderNotifyResult; import com.github.binarywang.wxpay.bean.notify.WxPayOrderNotifyResult;
@ -30,10 +33,6 @@ import java.time.LocalDateTime;
import java.time.ZoneId; import java.time.ZoneId;
import java.util.Objects; 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 实现类 * 微信支付公众号 PayClient 实现类
* *
@ -45,7 +44,7 @@ public class WXPubPayClient extends AbstractPayClient<WXPayClientConfig> {
private WxPayService client; private WxPayService client;
public WXPubPayClient(Long channelId, WXPayClientConfig config) { public WXPubPayClient(Long channelId, WXPayClientConfig config) {
super(channelId, PayChannelEnum.WX_PUB.getCode(), config, new WXCodeMapping()); super(channelId, PayChannelEnum.WX_PUB.getCode(), config);
} }
@Override @Override
@ -70,28 +69,30 @@ public class WXPubPayClient extends AbstractPayClient<WXPayClientConfig> {
} }
@Override @Override
public PayCommonResult<WxPayMpOrderResult> doUnifiedOrder(PayOrderUnifiedReqDTO reqDTO) { public PayOrderUnifiedRespDTO doUnifiedOrder(PayOrderUnifiedReqDTO reqDTO) {
WxPayMpOrderResult response; throw new UnsupportedOperationException();
try { //
switch (config.getApiVersion()) { // WxPayMpOrderResult response;
case WXPayClientConfig.API_VERSION_V2: // try {
response = this.unifiedOrderV2(reqDTO); // switch (config.getApiVersion()) {
break; // case WXPayClientConfig.API_VERSION_V2:
case WXPayClientConfig.API_VERSION_V3: // response = this.unifiedOrderV2(reqDTO);
WxPayUnifiedOrderV3Result.JsapiResult responseV3 = this.unifiedOrderV3(reqDTO); // break;
// V3 的结果统一转换成 V2返回的字段是一致的 // case WXPayClientConfig.API_VERSION_V3:
response = new WxPayMpOrderResult(); // WxPayUnifiedOrderV3Result.JsapiResult responseV3 = this.unifiedOrderV3(reqDTO);
BeanUtil.copyProperties(responseV3, response, true); // // V3 的结果统一转换成 V2返回的字段是一致的
break; // response = new WxPayMpOrderResult();
default: // BeanUtil.copyProperties(responseV3, response, true);
throw new IllegalArgumentException(String.format("未知的 API 版本(%s)", config.getApiVersion())); // break;
} // default:
} catch (WxPayException e) { // throw new IllegalArgumentException(String.format("未知的 API 版本(%s)", config.getApiVersion()));
log.error("[unifiedOrder][request({}) 发起支付失败,原因({})]", toJsonString(reqDTO), e); // }
return PayCommonResult.build(ObjectUtils.defaultIfNull(e.getErrCode(), e.getReturnCode(), "CustomErrorCode"), // } catch (WxPayException e) {
ObjectUtils.defaultIfNull(e.getErrCodeDes(), e.getCustomErrorMsg()),null, codeMapping); // log.error("[unifiedOrder][request({}) 发起支付失败,原因({})]", toJsonString(reqDTO), e);
} // return PayCommonResult.build(ObjectUtils.defaultIfNull(e.getErrCode(), e.getReturnCode(), "CustomErrorCode"),
return PayCommonResult.build(CODE_SUCCESS, MESSAGE_SUCCESS, response, codeMapping); // 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 支付回调对象 * @return 支付回调对象
* @throws WxPayException 微信异常类 * @throws WxPayException 微信异常类
*/ */
@Override // @Override
public PayOrderNotifyRespDTO parseOrderNotify(PayNotifyDataDTO data) throws WxPayException { public PayOrderNotifyRespDTO parseOrderNotify(PayNotifyReqDTO data) throws WxPayException {
log.info("[parseOrderNotify][微信支付回调data数据: {}]", data.getBody()); log.info("[parseOrderNotify][微信支付回调data数据: {}]", data.getBody());
// 微信支付 v2 回调结果处理 // 微信支付 v2 回调结果处理
switch (config.getApiVersion()) { 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 wxPayOrderNotifyV3Result = client.parseOrderNotifyV3Result(data.getBody(), null);
WxPayOrderNotifyV3Result.DecryptNotifyResult result = wxPayOrderNotifyV3Result.getResult(); WxPayOrderNotifyV3Result.DecryptNotifyResult result = wxPayOrderNotifyV3Result.getResult();
// 转换结果 // 转换结果
@ -165,11 +166,10 @@ public class WXPubPayClient extends AbstractPayClient<WXPayClientConfig> {
.orderExtensionNo(result.getOutTradeNo()) .orderExtensionNo(result.getOutTradeNo())
.channelOrderNo(result.getTradeState()) .channelOrderNo(result.getTradeState())
.successTime(LocalDateTimeUtil.parse(result.getSuccessTime(), "yyyy-MM-dd'T'HH:mm:ssXXX")) .successTime(LocalDateTimeUtil.parse(result.getSuccessTime(), "yyyy-MM-dd'T'HH:mm:ssXXX"))
.data(data.getBody())
.build(); .build();
} }
private PayOrderNotifyRespDTO parseOrderNotifyV2(PayNotifyDataDTO data) throws WxPayException { private PayOrderNotifyRespDTO parseOrderNotifyV2(PayNotifyReqDTO data) throws WxPayException {
WxPayOrderNotifyResult notifyResult = client.parseOrderNotifyResult(data.getBody()); WxPayOrderNotifyResult notifyResult = client.parseOrderNotifyResult(data.getBody());
Assert.isTrue(Objects.equals(notifyResult.getResultCode(), "SUCCESS"), "支付结果非 SUCCESS"); Assert.isTrue(Objects.equals(notifyResult.getResultCode(), "SUCCESS"), "支付结果非 SUCCESS");
// 转换结果 // 转换结果
@ -179,19 +179,12 @@ public class WXPubPayClient extends AbstractPayClient<WXPayClientConfig> {
.channelOrderNo(notifyResult.getTransactionId()) .channelOrderNo(notifyResult.getTransactionId())
.channelUserId(notifyResult.getOpenid()) .channelUserId(notifyResult.getOpenid())
.successTime(LocalDateTimeUtil.parse(notifyResult.getTimeEnd(), "yyyyMMddHHmmss")) .successTime(LocalDateTimeUtil.parse(notifyResult.getTimeEnd(), "yyyyMMddHHmmss"))
.data(data.getBody())
.build(); .build();
} }
@Override @Override
public PayRefundNotifyDTO parseRefundNotify(PayNotifyDataDTO notifyData) { protected PayRefundUnifiedRespDTO doUnifiedRefund(PayRefundUnifiedReqDTO reqDTO) throws Throwable {
// TODO 需要实现
throw new UnsupportedOperationException("需要实现");
}
@Override
protected PayCommonResult<PayRefundUnifiedRespDTO> doUnifiedRefund(PayRefundUnifiedReqDTO reqDTO) throws Throwable {
// TODO 需要实现 // TODO 需要实现
throw new UnsupportedOperationException(); throw new UnsupportedOperationException();
} }

View File

@ -25,7 +25,8 @@ public enum PayChannelEnum {
ALIPAY_PC("alipay_pc", "支付宝 PC 网站支付", AlipayPayClientConfig.class), ALIPAY_PC("alipay_pc", "支付宝 PC 网站支付", AlipayPayClientConfig.class),
ALIPAY_WAP("alipay_wap", "支付宝 Wap 网站支付", AlipayPayClientConfig.class), ALIPAY_WAP("alipay_wap", "支付宝 Wap 网站支付", AlipayPayClientConfig.class),
ALIPAY_APP("alipay_app", "支付宝App 支付", 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);
/** /**
* 编码 * 编码

View File

@ -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;
}

View File

@ -5,7 +5,7 @@ import cn.iocoder.yudao.framework.common.exception.ErrorCode;
/** /**
* 支付框架的错误码枚举 * 支付框架的错误码枚举
* *
* 短信框架使用 2-002-000-000 * 支付框架使用 2-002-000-000
* *
* @author 芋道源码 * @author 芋道源码
*/ */
@ -14,14 +14,16 @@ public interface PayFrameworkErrorCodeConstants {
ErrorCode PAY_UNKNOWN = new ErrorCode(2002000000, "未知错误,需要解析"); ErrorCode PAY_UNKNOWN = new ErrorCode(2002000000, "未知错误,需要解析");
// ========== 配置相关相关 2002000100 ========== // ========== 配置相关相关 2002000100 ==========
// todo 芋艿如下的错误码怎么处理掉
ErrorCode PAY_CONFIG_APP_ID_ERROR = new ErrorCode(2002000100, "支付渠道 AppId 不正确"); ErrorCode PAY_CONFIG_APP_ID_ERROR = new ErrorCode(2002000100, "支付渠道 AppId 不正确");
ErrorCode PAY_CONFIG_SIGN_ERROR = new ErrorCode(2002000100, "签名错误"); // 例如说微信支付配置错了 mchId 或者 mchKey ErrorCode PAY_CONFIG_SIGN_ERROR = new ErrorCode(2002000100, "签名错误"); // 例如说微信支付配置错了 mchId 或者 mchKey
// ========== 其它相关 2002000900 开头 ========== // ========== 其它相关 2002000900 开头 ==========
// todo 芋艿如下的错误码怎么处理掉
ErrorCode PAY_OPENID_ERROR = new ErrorCode(2002000900, "无效的 openid"); // 例如说微信 openid 未授权过 ErrorCode PAY_OPENID_ERROR = new ErrorCode(2002000900, "无效的 openid"); // 例如说微信 openid 未授权过
ErrorCode PAY_PARAM_MISSING = new ErrorCode(2002000901, "请求参数缺失"); // 例如说支付少传了金额 ErrorCode PAY_PARAM_MISSING = new ErrorCode(2002000901, "请求参数缺失"); // 例如说支付少传了金额
ErrorCode EXCEPTION = new ErrorCode(2002000999, "调用异常"); ErrorCode PAY_EXCEPTION = new ErrorCode(2002000999, "调用异常");
} }

View File

@ -6,6 +6,7 @@ package cn.iocoder.yudao.framework.pay.core.enums;
* @author jason * @author jason
*/ */
public enum PayNotifyRefundStatusEnum { public enum PayNotifyRefundStatusEnum {
/** /**
* 支付宝 全额退款 trade_status=TRADE_CLOSED 部分退款 trade_status=TRADE_SUCCESS * 支付宝 全额退款 trade_status=TRADE_CLOSED 部分退款 trade_status=TRADE_SUCCESS
* 退款成功 * 退款成功
@ -17,4 +18,5 @@ public enum PayNotifyRefundStatusEnum {
* 退款异常 * 退款异常
*/ */
ABNORMAL; ABNORMAL;
} }

View File

@ -5,7 +5,7 @@ import cn.hutool.core.util.RandomUtil;
import cn.iocoder.yudao.framework.common.pojo.CommonResult; import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.util.json.JsonUtils; 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.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.AlipayPayClientConfig;
import cn.iocoder.yudao.framework.pay.core.client.impl.alipay.AlipayQrPayClient; 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.AlipayWapPayClient;
@ -46,8 +46,8 @@ public class PayClientFactoryImplIntegrationTest {
PayClient client = payClientFactory.getPayClient(channelId); PayClient client = payClientFactory.getPayClient(channelId);
// 发起支付 // 发起支付
PayOrderUnifiedReqDTO reqDTO = buildPayOrderUnifiedReqDTO(); PayOrderUnifiedReqDTO reqDTO = buildPayOrderUnifiedReqDTO();
CommonResult<?> result = client.unifiedOrder(reqDTO); // CommonResult<?> result = client.unifiedOrder(reqDTO);
System.out.println(result); // System.out.println(result);
} }
/** /**
@ -69,8 +69,8 @@ public class PayClientFactoryImplIntegrationTest {
PayClient client = payClientFactory.getPayClient(channelId); PayClient client = payClientFactory.getPayClient(channelId);
// 发起支付 // 发起支付
PayOrderUnifiedReqDTO reqDTO = buildPayOrderUnifiedReqDTO(); PayOrderUnifiedReqDTO reqDTO = buildPayOrderUnifiedReqDTO();
CommonResult<?> result = client.unifiedOrder(reqDTO); // CommonResult<?> result = client.unifiedOrder(reqDTO);
System.out.println(result); // System.out.println(result);
} }
/** /**
@ -93,9 +93,9 @@ public class PayClientFactoryImplIntegrationTest {
// 发起支付 // 发起支付
PayOrderUnifiedReqDTO reqDTO = buildPayOrderUnifiedReqDTO(); PayOrderUnifiedReqDTO reqDTO = buildPayOrderUnifiedReqDTO();
reqDTO.setNotifyUrl("http://yunai.natapp1.cc/admin-api/pay/notify/callback/18"); // TODO @tina: 这里改成你的 natapp 回调地址 reqDTO.setNotifyUrl("http://yunai.natapp1.cc/admin-api/pay/notify/callback/18"); // TODO @tina: 这里改成你的 natapp 回调地址
CommonResult<AlipayTradePrecreateResponse> result = (CommonResult<AlipayTradePrecreateResponse>) client.unifiedOrder(reqDTO); // CommonResult<AlipayTradePrecreateResponse> result = (CommonResult<AlipayTradePrecreateResponse>) client.unifiedOrder(reqDTO);
System.out.println(JsonUtils.toJsonString(result)); // System.out.println(JsonUtils.toJsonString(result));
System.out.println(result.getData().getQrCode()); // System.out.println(result.getData().getQrCode());
} }
/** /**
@ -116,8 +116,8 @@ public class PayClientFactoryImplIntegrationTest {
PayClient client = payClientFactory.getPayClient(channelId); PayClient client = payClientFactory.getPayClient(channelId);
// 发起支付 // 发起支付
PayOrderUnifiedReqDTO reqDTO = buildPayOrderUnifiedReqDTO(); PayOrderUnifiedReqDTO reqDTO = buildPayOrderUnifiedReqDTO();
CommonResult<?> result = client.unifiedOrder(reqDTO); // CommonResult<?> result = client.unifiedOrder(reqDTO);
System.out.println(JsonUtils.toJsonString(result)); // System.out.println(JsonUtils.toJsonString(result));
} }
private static PayOrderUnifiedReqDTO buildPayOrderUnifiedReqDTO() { private static PayOrderUnifiedReqDTO buildPayOrderUnifiedReqDTO() {

View File

@ -1,8 +1,8 @@
package cn.iocoder.yudao.framework.pay.core.client.impl.alipay; package cn.iocoder.yudao.framework.pay.core.client.impl.alipay;
import cn.hutool.core.util.ReflectUtil; import cn.hutool.core.util.ReflectUtil;
import cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants; 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.order.PayOrderUnifiedReqDTO;
import cn.iocoder.yudao.framework.pay.core.client.dto.PayOrderUnifiedReqDTO; import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderUnifiedRespDTO;
import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest; import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest;
import com.alipay.api.AlipayApiException; import com.alipay.api.AlipayApiException;
import com.alipay.api.DefaultAlipayClient; import com.alipay.api.DefaultAlipayClient;
@ -87,13 +87,13 @@ public class AlipayQrPayClientTest extends BaseMockitoUnitTest {
}))).thenReturn(response); }))).thenReturn(response);
PayCommonResult<AlipayTradePrecreateResponse> result = client.doUnifiedOrder(reqDTO); // PayCommonResult<PayOrderUnifiedRespDTO> result = client.doUnifiedOrder(reqDTO);
// 断言 // // 断言
assertEquals(response.getCode(), result.getApiCode()); // assertEquals(response.getCode(), result.getApiCode());
assertEquals(response.getMsg(), result.getApiMsg()); // assertEquals(response.getMsg(), result.getApiMsg());
// TODO @tina这个断言木有过 // // TODO @tina这个断言木有过
assertEquals(GlobalErrorCodeConstants.SUCCESS.getCode(), result.getCode()); // assertEquals(GlobalErrorCodeConstants.SUCCESS.getCode(), result.getCode());
assertEquals(GlobalErrorCodeConstants.SUCCESS.getMsg(), result.getMsg()); // assertEquals(GlobalErrorCodeConstants.SUCCESS.getMsg(), result.getMsg());
} }
} }

View File

@ -1,5 +1,6 @@
package cn.iocoder.yudao.module.pay.api.notify.dto; package cn.iocoder.yudao.module.pay.api.notify.dto;
import cn.iocoder.yudao.module.pay.enums.refund.PayRefundStatusEnum;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.Builder; import lombok.Builder;
import lombok.Data; import lombok.Data;
@ -31,12 +32,4 @@ public class PayRefundNotifyReqDTO {
@NotNull(message = "支付退款编号不能为空") @NotNull(message = "支付退款编号不能为空")
private Long payRefundId; private Long payRefundId;
/**
* 退款状态
*
* (成功失败) TODO 芋艿枚举
*/
@NotNull(message = "退款状态不能为空")
private Integer status;
} }

View File

@ -28,12 +28,6 @@ public class PayRefundCreateReqDTO {
// ========== 商户相关字段 ========== // ========== 商户相关字段 ==========
/**
* 商户订单编号
*/
@NotEmpty(message = "商户订单编号不能为空")
private String merchantOrderId;
/** /**
* 退款描述 * 退款描述
*/ */
@ -43,6 +37,12 @@ public class PayRefundCreateReqDTO {
// ========== 订单相关字段 ========== // ========== 订单相关字段 ==========
/**
* 支付单号
*/
@NotNull(message = "支付单号不能为空")
private Long payOrderId;
/** /**
* 退款金额单位 * 退款金额单位
*/ */

View File

@ -27,8 +27,16 @@ public class PayRefundRespDTO {
* 枚举 {@link PayRefundStatusEnum} * 枚举 {@link PayRefundStatusEnum}
*/ */
private Integer status; private Integer status;
/**
* 退款金额单位
*/
private Integer refundAmount;
// ========== 渠道相关字段 ========== // ========== 商户相关字段 ==========
/**
* 商户订单编号
*/
private String merchantOrderId;
/** /**
* 退款成功时间 * 退款成功时间
*/ */

View File

@ -1,5 +1,6 @@
package cn.iocoder.yudao.module.pay.enums; package cn.iocoder.yudao.module.pay.enums;
import cn.hutool.core.util.ObjectUtil;
import cn.iocoder.yudao.framework.common.exception.ErrorCode; 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_SUCCEED = new ErrorCode(1007006003, "已经退款成功");
ErrorCode PAY_REFUND_NOT_FOUND = new ErrorCode(1007006004, "支付退款单不存在"); ErrorCode PAY_REFUND_NOT_FOUND = new ErrorCode(1007006004, "支付退款单不存在");
/** /**
* ========== 支付商户信息 1-007-004-000 ========== * ========== 支付商户信息 1-007-004-000 ==========
*/ */
ErrorCode PAY_MERCHANT_NOT_EXISTS = new ErrorCode(1007004000, "支付商户信息不存在"); ErrorCode PAY_MERCHANT_NOT_EXISTS = new ErrorCode(1007004000, "支付商户信息不存在");
ErrorCode PAY_MERCHANT_EXIST_APP_CANT_DELETE = new ErrorCode(1007004001, "支付商户存在支付应用,无法删除"); 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, "发起退款失败,退款单金额不匹配");
} }

View File

@ -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.PayRefundCreateReqDTO;
import cn.iocoder.yudao.module.pay.api.refund.dto.PayRefundRespDTO; 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.stereotype.Service;
import org.springframework.validation.annotation.Validated; import org.springframework.validation.annotation.Validated;
import javax.annotation.Resource;
/** /**
* 退款单 API 实现类 * 退款单 API 实现类
* *
@ -14,10 +17,12 @@ import org.springframework.validation.annotation.Validated;
@Validated @Validated
public class PayRefundApiImpl implements PayRefundApi { public class PayRefundApiImpl implements PayRefundApi {
@Resource
private PayRefundService payRefundService;
@Override @Override
public Long createPayRefund(PayRefundCreateReqDTO reqDTO) { public Long createPayRefund(PayRefundCreateReqDTO reqDTO) {
// TODO 芋艿暂未实现 return payRefundService.createPayRefund(reqDTO);
return null;
} }
@Override @Override

View File

@ -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.CommonResult;
import cn.iocoder.yudao.framework.common.pojo.PageParam; import cn.iocoder.yudao.framework.common.pojo.PageParam;
import cn.iocoder.yudao.framework.common.pojo.PageResult; 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.PayDemoOrderCreateReqVO;
import cn.iocoder.yudao.module.pay.controller.admin.demo.vo.PayDemoOrderRespVO; 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.convert.demo.PayDemoOrderConvert;
import cn.iocoder.yudao.module.pay.dal.dataobject.demo.PayDemoOrderDO; import cn.iocoder.yudao.module.pay.dal.dataobject.demo.PayDemoOrderDO;
import cn.iocoder.yudao.module.pay.service.demo.PayDemoOrderService; import cn.iocoder.yudao.module.pay.service.demo.PayDemoOrderService;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.validation.annotation.Validated; import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource; import javax.annotation.Resource;
import javax.annotation.security.PermitAll;
import javax.validation.Valid; import javax.validation.Valid;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; 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; import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
@Tag(name = "管理后台 - 示例订单") @Tag(name = "管理后台 - 示例订单")
@ -41,4 +47,32 @@ public class PayDemoOrderController {
return success(PayDemoOrderConvert.INSTANCE.convertPage(pageResult)); 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);
}
} }

View File

@ -12,6 +12,9 @@ import java.time.LocalDateTime;
@Data @Data
public class PayDemoOrderRespVO { public class PayDemoOrderRespVO {
@Schema(description = "订单编号", required = true, example = "1024")
private Long id;
@Schema(description = "用户编号", required = true, example = "23199") @Schema(description = "用户编号", required = true, example = "23199")
private Long userId; private Long userId;
@ -33,10 +36,19 @@ public class PayDemoOrderRespVO {
@Schema(description = "订单支付时间") @Schema(description = "订单支付时间")
private LocalDateTime payTime; private LocalDateTime payTime;
@Schema(description = "支付渠道", example = "alipay_qr")
private String payChannelCode;
@Schema(description = "支付退款编号", example = "23366")
private Long payRefundId;
@Schema(description = "退款金额,单位:分", required = true, example = "14039") @Schema(description = "退款金额,单位:分", required = true, example = "14039")
private Integer refundPrice; private Integer refundPrice;
@Schema(description = "退款时间") @Schema(description = "退款时间")
private LocalDateTime refundTime; private LocalDateTime refundTime;
@Schema(description = "创建时间", required = true)
private LocalDateTime createTime;
} }

View File

@ -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.operatelog.core.annotations.OperateLog;
import cn.iocoder.yudao.framework.pay.core.client.PayClient; 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.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.order.PayOrderService;
import cn.iocoder.yudao.module.pay.service.refund.PayRefundService; import cn.iocoder.yudao.module.pay.service.refund.PayRefundService;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
@ -17,6 +21,7 @@ import javax.annotation.security.PermitAll;
import java.util.Map; import java.util.Map;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; 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; import static cn.iocoder.yudao.module.pay.enums.ErrorCodeConstants.PAY_CHANNEL_CLIENT_NOT_FOUND;
@Tag(name = "管理后台 - 支付通知") @Tag(name = "管理后台 - 支付通知")
@ -64,28 +69,32 @@ public class PayNotifyController {
@PermitAll @PermitAll
@OperateLog(enable = false) // 回调地址无需记录操作日志 @OperateLog(enable = false) // 回调地址无需记录操作日志
public String notifyCallback(@PathVariable("channelId") Long channelId, public String notifyCallback(@PathVariable("channelId") Long channelId,
@RequestParam Map<String, String> params, @RequestParam(required = false) Map<String, String> params,
@RequestBody String body) throws Exception { @RequestBody(required = false) String body) {
// 校验支付渠道是否存在 log.info("[notifyCallback][channelId({}) 回调数据({}/{})]", channelId, params, body);
// 1. 校验支付渠道是否存在
PayClient payClient = payClientFactory.getPayClient(channelId); PayClient payClient = payClientFactory.getPayClient(channelId);
if (payClient == null) { if (payClient == null) {
log.error("[notifyCallback][渠道编号({}) 找不到对应的支付客户端]", channelId); log.error("[notifyCallback][渠道编号({}) 找不到对应的支付客户端]", channelId);
throw exception(PAY_CHANNEL_CLIENT_NOT_FOUND); throw exception(PAY_CHANNEL_CLIENT_NOT_FOUND);
} }
// 校验通知数据是否合法
PayNotifyDataDTO notifyData = PayNotifyDataDTO.builder().params(params).body(body).build();
payClient.verifyNotifyData(notifyData);
// 情况一如果是退款则发起退款通知 // 2. 解析通知数据
if (payClient.isRefundNotify(notifyData)) { PayNotifyReqDTO rawNotify = PayNotifyReqDTO.builder().params(params).body(body).build();
refundService.notifyPayRefund(channelId, PayNotifyDataDTO.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"; return "success";
} }
// 3.2支付通知
// 情况二如果非退款则发起支付通知 if (notify instanceof PayOrderNotifyRespDTO) {
orderService.notifyPayOrder(channelId, PayNotifyDataDTO.builder().params(params).body(body).build()); orderService.notifyPayOrder(channelId, (PayOrderNotifyRespDTO) notify, rawNotify);
return "success"; return "success";
}
throw new UnsupportedOperationException("未知通知:" + toJsonString(notify));
} }
} }

View File

@ -2,6 +2,12 @@ package cn.iocoder.yudao.module.pay.controller.admin.order;
import cn.hutool.core.collection.CollectionUtil; import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.ObjectUtil; 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.controller.admin.order.vo.*;
import cn.iocoder.yudao.module.pay.convert.order.PayOrderConvert; 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.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.merchant.PayMerchantService;
import cn.iocoder.yudao.module.pay.service.order.PayOrderExtensionService; 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.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.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.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated; import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.*;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource; import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
@ -37,6 +34,7 @@ import java.util.List;
import java.util.Map; import java.util.Map;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; 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; import static cn.iocoder.yudao.framework.operatelog.core.enums.OperateTypeEnum.EXPORT;
@Tag(name = "管理后台 - 支付订单") @Tag(name = "管理后台 - 支付订单")
@ -46,7 +44,7 @@ import static cn.iocoder.yudao.framework.operatelog.core.enums.OperateTypeEnum.E
public class PayOrderController { public class PayOrderController {
@Resource @Resource
private PayOrderService orderService; private PayOrderService payOrderService;
@Resource @Resource
private PayOrderExtensionService orderExtensionService; private PayOrderExtensionService orderExtensionService;
@Resource @Resource
@ -58,8 +56,17 @@ public class PayOrderController {
@Operation(summary = "获得支付订单") @Operation(summary = "获得支付订单")
@Parameter(name = "id", description = "编号", required = true, example = "1024") @Parameter(name = "id", description = "编号", required = true, example = "1024")
@PreAuthorize("@ss.hasPermission('pay:order:query')") @PreAuthorize("@ss.hasPermission('pay:order:query')")
public CommonResult<PayOrderDetailsRespVO> getOrder(@RequestParam("id") Long id) { public CommonResult<PayOrderRespVO> getOrder(@RequestParam("id") Long id) {
PayOrderDO order = orderService.getOrder(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)) { if (ObjectUtil.isNull(order)) {
return success(new PayOrderDetailsRespVO()); return success(new PayOrderDetailsRespVO());
} }
@ -82,11 +89,18 @@ public class PayOrderController {
return success(respVO); 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") @GetMapping("/page")
@Operation(summary = "获得支付订单分页") @Operation(summary = "获得支付订单分页")
@PreAuthorize("@ss.hasPermission('pay:order:query')") @PreAuthorize("@ss.hasPermission('pay:order:query')")
public CommonResult<PageResult<PayOrderPageItemRespVO>> getOrderPage(@Valid PayOrderPageReqVO pageVO) { 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())) { if (CollectionUtil.isEmpty(pageResult.getList())) {
return success(new PageResult<>(pageResult.getTotal())); return success(new PageResult<>(pageResult.getTotal()));
} }
@ -120,7 +134,7 @@ public class PayOrderController {
public void exportOrderExcel(@Valid PayOrderExportReqVO exportReqVO, public void exportOrderExcel(@Valid PayOrderExportReqVO exportReqVO,
HttpServletResponse response) throws IOException { HttpServletResponse response) throws IOException {
List<PayOrderDO> list = orderService.getOrderList(exportReqVO); List<PayOrderDO> list = payOrderService.getOrderList(exportReqVO);
if (CollectionUtil.isEmpty(list)) { if (CollectionUtil.isEmpty(list)) {
ExcelUtils.write(response, "支付订单.xls", "数据", ExcelUtils.write(response, "支付订单.xls", "数据",
PayOrderExcelVO.class, new ArrayList<>()); PayOrderExcelVO.class, new ArrayList<>());

View File

@ -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;
}

View File

@ -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;
}

View File

@ -1,15 +1,13 @@
package cn.iocoder.yudao.module.pay.controller.app.order; 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.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.AppPayOrderSubmitReqVO;
import cn.iocoder.yudao.module.pay.controller.app.order.vo.AppPayOrderSubmitRespVO; 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.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.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.validation.annotation.Validated; import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PostMapping;
@ -34,20 +32,9 @@ public class AppPayOrderController {
@PostMapping("/submit") @PostMapping("/submit")
@Operation(summary = "提交支付订单") @Operation(summary = "提交支付订单")
// @PreAuthenticated // TODO 暂时不加登陆验证前端暂时没做好
public CommonResult<AppPayOrderSubmitRespVO> submitPayOrder(@RequestBody AppPayOrderSubmitReqVO reqVO) { public CommonResult<AppPayOrderSubmitRespVO> submitPayOrder(@RequestBody AppPayOrderSubmitReqVO reqVO) {
// 获得订单 PayOrderSubmitRespVO respVO = orderService.submitPayOrder(reqVO, getClientIP());
PayOrderDO payOrder = orderService.getOrder(reqVO.getId()); return success(PayOrderConvert.INSTANCE.convert3(respVO));
// 提交支付
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());
} }
} }

View File

@ -1,5 +1,6 @@
package cn.iocoder.yudao.module.pay.controller.app.order.vo; 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 io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data; import lombok.Data;
import lombok.experimental.Accessors; import lombok.experimental.Accessors;
@ -10,18 +11,5 @@ import java.util.Map;
@Schema(description = "用户 APP - 支付订单提交 Request VO") @Schema(description = "用户 APP - 支付订单提交 Request VO")
@Data @Data
@Accessors(chain = true) public class AppPayOrderSubmitReqVO extends PayOrderSubmitReqVO {
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;
} }

View File

@ -1,5 +1,6 @@
package cn.iocoder.yudao.module.pay.controller.app.order.vo; 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 io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.Builder; import lombok.Builder;
@ -9,15 +10,6 @@ import lombok.experimental.Accessors;
@Schema(description = "用户 APP - 支付订单提交 Response VO") @Schema(description = "用户 APP - 支付订单提交 Response VO")
@Data @Data
@Accessors(chain = true) public class AppPayOrderSubmitRespVO extends PayOrderSubmitRespVO {
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class AppPayOrderSubmitRespVO {
/**
* 调用支付渠道的响应结果
*/
private Object invokeResponse;
} }

View File

@ -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)));
}
}

View File

@ -0,0 +1,4 @@
/**
* TODO 芋艿占个位置没啥用
*/
package cn.iocoder.yudao.module.pay.controller.app.refund;

View File

@ -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;
}

View File

@ -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;
}

View File

@ -1,16 +1,15 @@
package cn.iocoder.yudao.module.pay.convert.order; package cn.iocoder.yudao.module.pay.convert.order;
import cn.iocoder.yudao.framework.common.pojo.PageResult; 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.PayOrderCreateReqDTO;
import cn.iocoder.yudao.module.pay.api.order.dto.PayOrderRespDTO; 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.*;
import cn.iocoder.yudao.module.pay.controller.admin.order.vo.PayOrderExcelVO; import cn.iocoder.yudao.module.pay.controller.app.order.vo.AppPayOrderSubmitReqVO;
import cn.iocoder.yudao.module.pay.controller.admin.order.vo.PayOrderPageItemRespVO; import cn.iocoder.yudao.module.pay.controller.app.order.vo.AppPayOrderSubmitRespVO;
import cn.iocoder.yudao.module.pay.controller.admin.order.vo.PayOrderRespVO;
import cn.iocoder.yudao.module.pay.dal.dataobject.order.PayOrderDO; import cn.iocoder.yudao.module.pay.dal.dataobject.order.PayOrderDO;
import cn.iocoder.yudao.module.pay.dal.dataobject.order.PayOrderExtensionDO; import cn.iocoder.yudao.module.pay.dal.dataobject.order.PayOrderExtensionDO;
import cn.iocoder.yudao.module.pay.service.order.dto.PayOrderSubmitReqDTO;
import org.mapstruct.Mapper; import org.mapstruct.Mapper;
import org.mapstruct.Mapping; import org.mapstruct.Mapping;
import org.mapstruct.factory.Mappers; import org.mapstruct.factory.Mappers;
@ -31,6 +30,8 @@ public interface PayOrderConvert {
PayOrderRespVO convert(PayOrderDO bean); PayOrderRespVO convert(PayOrderDO bean);
PayOrderRespDTO convert2(PayOrderDO order);
PayOrderDetailsRespVO orderDetailConvert(PayOrderDO bean); PayOrderDetailsRespVO orderDetailConvert(PayOrderDO bean);
PayOrderDetailsRespVO.PayOrderExtension orderDetailExtensionConvert(PayOrderExtensionDO bean); PayOrderDetailsRespVO.PayOrderExtension orderDetailExtensionConvert(PayOrderExtensionDO bean);
@ -88,14 +89,15 @@ public interface PayOrderConvert {
return payOrderExcelVO; return payOrderExcelVO;
} }
PayOrderDO convert(PayOrderCreateReqDTO bean); PayOrderDO convert(PayOrderCreateReqDTO bean);
@Mapping(target = "id", ignore = true) @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);
} }

View File

@ -2,12 +2,8 @@ package cn.iocoder.yudao.module.pay.convert.refund;
import cn.iocoder.yudao.framework.common.pojo.PageResult; 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.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.order.PayOrderDO;
import cn.iocoder.yudao.module.pay.dal.dataobject.refund.PayRefundDO; 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.Mapper;
import org.mapstruct.Mapping; import org.mapstruct.Mapping;
import org.mapstruct.Mappings; import org.mapstruct.Mappings;
@ -17,11 +13,6 @@ import java.math.BigDecimal;
import java.math.RoundingMode; import java.math.RoundingMode;
import java.util.List; import java.util.List;
/**
* 退款订单 Convert
*
* @author aquan
*/
@Mapper @Mapper
public interface PayRefundConvert { public interface PayRefundConvert {
@ -102,8 +93,4 @@ public interface PayRefundConvert {
}) })
PayRefundDO convert(PayOrderDO orderDO); PayRefundDO convert(PayOrderDO orderDO);
PayRefundReqDTO convert(AppPayRefundReqVO bean);
AppPayRefundRespVO convert(PayRefundRespDTO bean);
} }

View File

@ -73,14 +73,16 @@ public class PayDemoOrderDO extends BaseDO {
// ========== 退款相关字段 ========== // ========== 退款相关字段 ==========
/** /**
* 退款金额 * 支付退款单号
*/
private Long payRefundId;
/**
* 退款金额单位
*/ */
private Integer refundPrice; private Integer refundPrice;
/** /**
* 退款时间 * 退款完成时间
*
* 由于可以多次退款记录最后一次退款的时间
*/ */
private Date refundTime; private LocalDateTime refundTime;
} }

View File

@ -80,7 +80,6 @@ public class PayRefundDO extends BaseDO {
*/ */
private String tradeNo; private String tradeNo;
// ========== 商户相关字段 ========== // ========== 商户相关字段 ==========
/** /**
* 商户订单编号 * 商户订单编号
@ -171,14 +170,12 @@ public class PayRefundDO extends BaseDO {
*/ */
private String channelErrorMsg; private String channelErrorMsg;
/** /**
* 支付渠道的额外参数 * 支付渠道的额外参数
* 参见 https://www.pingxx.com/api/Refunds%20退款概述.html * 参见 https://www.pingxx.com/api/Refunds%20退款概述.html
*/ */
private String channelExtras; private String channelExtras;
/** /**
* TODO * TODO
* 退款失效时间 * 退款失效时间
@ -193,5 +190,4 @@ public class PayRefundDO extends BaseDO {
*/ */
private LocalDateTime notifyTime; private LocalDateTime notifyTime;
} }

View File

@ -20,4 +20,9 @@ public interface PayDemoOrderMapper extends BaseMapperX<PayDemoOrderDO> {
.orderByDesc(PayDemoOrderDO::getId)); .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));
}
} }

View File

@ -5,5 +5,5 @@ import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Mapper;
@Mapper @Mapper
public interface PayNotifyLogCoreMapper extends BaseMapperX<PayNotifyLogDO> { public interface PayNotifyLogMapper extends BaseMapperX<PayNotifyLogDO> {
} }

View File

@ -10,7 +10,7 @@ import java.time.LocalDateTime;
import java.util.List; import java.util.List;
@Mapper @Mapper
public interface PayNotifyTaskCoreMapper extends BaseMapperX<PayNotifyTaskDO> { public interface PayNotifyTaskMapper extends BaseMapperX<PayNotifyTaskDO> {
/** /**
* 获得需要通知的 PayNotifyTaskDO 记录需要满足如下条件 * 获得需要通知的 PayNotifyTaskDO 记录需要满足如下条件

View File

@ -39,4 +39,28 @@ public interface PayDemoOrderService {
*/ */
PageResult<PayDemoOrderDO> getDemoOrderPage(PageParam pageReqVO); 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);
} }

View File

@ -1,23 +1,38 @@
package cn.iocoder.yudao.module.pay.service.demo; package cn.iocoder.yudao.module.pay.service.demo;
import cn.hutool.core.lang.Assert; 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.PageParam;
import cn.iocoder.yudao.framework.common.pojo.PageResult; 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.PayOrderApi;
import cn.iocoder.yudao.module.pay.api.order.dto.PayOrderCreateReqDTO; 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.controller.admin.demo.vo.PayDemoOrderCreateReqVO;
import cn.iocoder.yudao.module.pay.dal.dataobject.demo.PayDemoOrderDO; 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.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.stereotype.Service;
import org.springframework.validation.annotation.Validated; import org.springframework.validation.annotation.Validated;
import javax.annotation.Resource; import javax.annotation.Resource;
import java.time.Duration; import java.time.Duration;
import java.time.LocalDateTime;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; 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.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.framework.common.util.servlet.ServletUtils.getClientIP;
import static cn.iocoder.yudao.module.pay.enums.ErrorCodeConstants.*;
/** /**
* 示例订单 Service 实现类 * 示例订单 Service 实现类
@ -26,6 +41,7 @@ import static cn.iocoder.yudao.framework.common.util.servlet.ServletUtils.getCli
*/ */
@Service @Service
@Validated @Validated
@Slf4j
public class PayDemoOrderServiceImpl implements PayDemoOrderService { public class PayDemoOrderServiceImpl implements PayDemoOrderService {
/** /**
@ -45,6 +61,8 @@ public class PayDemoOrderServiceImpl implements PayDemoOrderService {
@Resource @Resource
private PayOrderApi payOrderApi; private PayOrderApi payOrderApi;
@Resource
private PayRefundApi payRefundApi;
@Resource @Resource
private PayDemoOrderMapper payDemoOrderMapper; private PayDemoOrderMapper payDemoOrderMapper;
@ -53,8 +71,8 @@ public class PayDemoOrderServiceImpl implements PayDemoOrderService {
spuNames.put(1L, new Object[]{"华为手机", 1}); spuNames.put(1L, new Object[]{"华为手机", 1});
spuNames.put(2L, new Object[]{"小米电视", 10}); spuNames.put(2L, new Object[]{"小米电视", 10});
spuNames.put(3L, new Object[]{"苹果手表", 100}); spuNames.put(3L, new Object[]{"苹果手表", 100});
spuNames.put(4L, new Object[]{"华硕笔记本", 200}); spuNames.put(4L, new Object[]{"华硕笔记本", 1000});
spuNames.put(5L, new Object[]{"蔚来汽车", 300}); spuNames.put(5L, new Object[]{"蔚来汽车", 200000});
} }
@Override @Override
@ -67,7 +85,7 @@ public class PayDemoOrderServiceImpl implements PayDemoOrderService {
// 1.2 插入 demo 订单 // 1.2 插入 demo 订单
PayDemoOrderDO demoOrder = new PayDemoOrderDO().setUserId(userId) PayDemoOrderDO demoOrder = new PayDemoOrderDO().setUserId(userId)
.setSpuId(createReqVO.getSpuId()).setSpuName(spuName) .setSpuId(createReqVO.getSpuId()).setSpuName(spuName)
.setPayed(false).setRefundPrice(0); .setPrice(price).setPayed(false).setRefundPrice(0);
payDemoOrderMapper.insert(demoOrder); payDemoOrderMapper.insert(demoOrder);
// 2.1 创建支付单 // 2.1 创建支付单
@ -99,4 +117,152 @@ public class PayDemoOrderServiceImpl implements PayDemoOrderService {
return payDemoOrderMapper.selectPage(pageReqVO); 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;
}
} }

View File

@ -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.notify.PayNotifyTaskDO;
import cn.iocoder.yudao.module.pay.dal.dataobject.order.PayOrderDO; import cn.iocoder.yudao.module.pay.dal.dataobject.order.PayOrderDO;
import cn.iocoder.yudao.module.pay.dal.dataobject.refund.PayRefundDO; import cn.iocoder.yudao.module.pay.dal.dataobject.refund.PayRefundDO;
import cn.iocoder.yudao.module.pay.dal.mysql.notify.PayNotifyLogCoreMapper; import cn.iocoder.yudao.module.pay.dal.mysql.notify.PayNotifyLogMapper;
import cn.iocoder.yudao.module.pay.dal.mysql.notify.PayNotifyTaskCoreMapper; 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.dal.redis.notify.PayNotifyLockRedisDAO;
import cn.iocoder.yudao.module.pay.enums.notify.PayNotifyStatusEnum; import cn.iocoder.yudao.module.pay.enums.notify.PayNotifyStatusEnum;
import cn.iocoder.yudao.module.pay.enums.notify.PayNotifyTypeEnum; 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.annotation.Resource;
import javax.validation.Valid; import javax.validation.Valid;
import java.time.Duration;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
@ -40,6 +41,7 @@ import java.util.Objects;
import java.util.concurrent.CountDownLatch; import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit; 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; 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; private PayRefundService refundService;
@Resource @Resource
private PayNotifyTaskCoreMapper payNotifyTaskCoreMapper; private PayNotifyTaskMapper payNotifyTaskMapper;
@Resource @Resource
private PayNotifyLogCoreMapper payNotifyLogCoreMapper; private PayNotifyLogMapper payNotifyLogMapper;
@Resource(name = NOTIFY_THREAD_POOL_TASK_EXECUTOR) @Resource(name = NOTIFY_THREAD_POOL_TASK_EXECUTOR)
private ThreadPoolTaskExecutor threadPoolTaskExecutor; private ThreadPoolTaskExecutor threadPoolTaskExecutor;
@ -101,7 +103,7 @@ public class PayNotifyServiceImpl implements PayNotifyService {
} }
// 执行插入 // 执行插入
payNotifyTaskCoreMapper.insert(task); payNotifyTaskMapper.insert(task);
// 异步直接发起任务虽然会有定时任务扫描但是会导致延迟 // 异步直接发起任务虽然会有定时任务扫描但是会导致延迟
self.executeNotifyAsync(task); self.executeNotifyAsync(task);
@ -110,7 +112,7 @@ public class PayNotifyServiceImpl implements PayNotifyService {
@Override @Override
public int executeNotify() throws InterruptedException { public int executeNotify() throws InterruptedException {
// 获得需要通知的任务 // 获得需要通知的任务
List<PayNotifyTaskDO> tasks = payNotifyTaskCoreMapper.selectListByNotify(); List<PayNotifyTaskDO> tasks = payNotifyTaskMapper.selectListByNotify();
if (CollUtil.isEmpty(tasks)) { if (CollUtil.isEmpty(tasks)) {
return 0; return 0;
} }
@ -168,8 +170,8 @@ public class PayNotifyServiceImpl implements PayNotifyService {
payNotifyLockCoreRedisDAO.lock(task.getId(), NOTIFY_TIMEOUT_MILLIS, () -> { payNotifyLockCoreRedisDAO.lock(task.getId(), NOTIFY_TIMEOUT_MILLIS, () -> {
// 校验当前任务是否已经被通知过 // 校验当前任务是否已经被通知过
// 虽然已经通过分布式加锁但是可能同时满足通知的条件然后都去获得锁此时第一个执行完后第二个还是能拿到锁然后会再执行一次 // 虽然已经通过分布式加锁但是可能同时满足通知的条件然后都去获得锁此时第一个执行完后第二个还是能拿到锁然后会再执行一次
PayNotifyTaskDO dbTask = payNotifyTaskCoreMapper.selectById(task.getId()); PayNotifyTaskDO dbTask = payNotifyTaskMapper.selectById(task.getId());
if (LocalDateTimeUtils.afterNow(dbTask.getNextNotifyTime())) { if (afterNow(dbTask.getNextNotifyTime())) {
log.info("[executeNotifySync][dbTask({}) 任务被忽略,原因是未到达下次通知时间,可能是因为并发执行了]", log.info("[executeNotifySync][dbTask({}) 任务被忽略,原因是未到达下次通知时间,可能是因为并发执行了]",
JsonUtils.toJsonString(dbTask)); JsonUtils.toJsonString(dbTask));
return; return;
@ -197,7 +199,7 @@ public class PayNotifyServiceImpl implements PayNotifyService {
// 记录 PayNotifyLog 日志 // 记录 PayNotifyLog 日志
String response = invokeException != null ? ExceptionUtil.getRootCauseMessage(invokeException) : String response = invokeException != null ? ExceptionUtil.getRootCauseMessage(invokeException) :
JsonUtils.toJsonString(invokeResult); 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()); .notifyTimes(task.getNotifyTimes() + 1).status(newStatus).response(response).build());
} }
@ -250,23 +252,22 @@ public class PayNotifyServiceImpl implements PayNotifyService {
// 情况一调用成功 // 情况一调用成功
if (invokeResult != null && invokeResult.isSuccess()) { if (invokeResult != null && invokeResult.isSuccess()) {
updateTask.setStatus(PayNotifyStatusEnum.SUCCESS.getStatus()); updateTask.setStatus(PayNotifyStatusEnum.SUCCESS.getStatus());
payNotifyTaskMapper.updateById(updateTask);
return updateTask.getStatus(); return updateTask.getStatus();
} }
// 情况二调用失败调用异常 // 情况二调用失败调用异常
// 2.1 超过最大回调次数 // 2.1 超过最大回调次数
if (updateTask.getNotifyTimes() >= PayNotifyTaskDO.NOTIFY_FREQUENCY.length) { if (updateTask.getNotifyTimes() >= PayNotifyTaskDO.NOTIFY_FREQUENCY.length) {
updateTask.setStatus(PayNotifyStatusEnum.FAILURE.getStatus()); updateTask.setStatus(PayNotifyStatusEnum.FAILURE.getStatus());
payNotifyTaskMapper.updateById(updateTask);
return updateTask.getStatus(); return updateTask.getStatus();
} }
// 2.2 未超过最大回调次数 // 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() updateTask.setStatus(invokeException != null ? PayNotifyStatusEnum.REQUEST_FAILURE.getStatus()
: PayNotifyStatusEnum.REQUEST_SUCCESS.getStatus()); : PayNotifyStatusEnum.REQUEST_SUCCESS.getStatus());
payNotifyTaskMapper.updateById(updateTask);
return updateTask.getStatus(); return updateTask.getStatus();
} }
private void processNotifySuccess(PayNotifyTaskDO task, PayNotifyTaskDO updateTask) {
payNotifyTaskCoreMapper.updateById(updateTask);
}
} }

View File

@ -1,16 +1,18 @@
package cn.iocoder.yudao.module.pay.service.order; 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.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; 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.api.order.dto.PayOrderCreateReqDTO;
import cn.iocoder.yudao.module.pay.service.order.dto.PayOrderSubmitReqDTO; import cn.iocoder.yudao.module.pay.controller.admin.order.vo.PayOrderExportReqVO;
import cn.iocoder.yudao.module.pay.service.order.dto.PayOrderSubmitRespDTO; 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.Valid;
import javax.validation.constraints.NotEmpty;
import java.util.Collection; import java.util.Collection;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@ -81,17 +83,20 @@ public interface PayOrderService {
* 提交支付 * 提交支付
* 此时会发起支付渠道的调用 * 此时会发起支付渠道的调用
* *
* @param reqDTO 提交请求 * @param reqVO 提交请求
* @param userIp 提交 IP
* @return 提交结果 * @return 提交结果
*/ */
PayOrderSubmitRespDTO submitPayOrder(@Valid PayOrderSubmitReqDTO reqDTO); PayOrderSubmitRespVO submitPayOrder(@Valid PayOrderSubmitReqVO reqVO,
@NotEmpty(message = "提交 IP 不能为空") String userIp);
/** /**
* 通知支付单成功 * 通知支付单成功
* *
* @param channelId 渠道编号 * @param channelId 渠道编号
* @param notifyData 通知数据 * @param notify 通知
* @param rawNotify 通知数据
*/ */
void notifyPayOrder(Long channelId, PayNotifyDataDTO notifyData) throws Exception; void notifyPayOrder(Long channelId, PayOrderNotifyRespDTO notify, PayNotifyReqDTO rawNotify);
} }

View File

@ -3,19 +3,21 @@ package cn.iocoder.yudao.module.pay.service.order;
import cn.hutool.core.date.DateUtil; import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.RandomUtil; 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.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.json.JsonUtils; import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
import cn.iocoder.yudao.framework.pay.config.PayProperties; 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.PayClient;
import cn.iocoder.yudao.framework.pay.core.client.PayClientFactory; 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.PayOrderNotifyRespDTO; import cn.iocoder.yudao.framework.pay.core.client.dto.notify.PayOrderNotifyRespDTO;
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.tenant.core.util.TenantUtils; 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.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.PayOrderExportReqVO;
import cn.iocoder.yudao.module.pay.controller.admin.order.vo.PayOrderPageReqVO; 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.convert.order.PayOrderConvert;
import cn.iocoder.yudao.module.pay.dal.dataobject.merchant.PayAppDO; import cn.iocoder.yudao.module.pay.dal.dataobject.merchant.PayAppDO;
import cn.iocoder.yudao.module.pay.dal.dataobject.merchant.PayChannelDO; 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.merchant.PayChannelService;
import cn.iocoder.yudao.module.pay.service.notify.PayNotifyService; 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.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 lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
@ -42,9 +42,9 @@ import javax.annotation.Resource;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.util.Collection; import java.util.Collection;
import java.util.List; 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.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.framework.common.util.json.JsonUtils.*;
/** /**
* 支付订单 Service 实现类 * 支付订单 Service 实现类
@ -105,7 +105,7 @@ public class PayOrderServiceImpl implements PayOrderService {
reqDTO.getAppId(), reqDTO.getMerchantOrderId()); reqDTO.getAppId(), reqDTO.getMerchantOrderId());
if (order != null) { if (order != null) {
log.warn("[createPayOrder][appId({}) merchantOrderId({}) 已经存在对应的支付单({})]", order.getAppId(), log.warn("[createPayOrder][appId({}) merchantOrderId({}) 已经存在对应的支付单({})]", order.getAppId(),
order.getMerchantOrderId(), JsonUtils.toJsonString(order)); // 理论来说不会出现这个情况 order.getMerchantOrderId(), toJsonString(order)); // 理论来说不会出现这个情况
return order.getId(); return order.getId();
} }
@ -127,51 +127,60 @@ public class PayOrderServiceImpl implements PayOrderService {
} }
@Override @Override
public PayOrderSubmitRespDTO submitPayOrder(PayOrderSubmitReqDTO reqDTO) { public PayOrderSubmitRespVO submitPayOrder(PayOrderSubmitReqVO reqVO, String userIp) {
// 校验 App // 1. 获得 PayOrderDO 并校验其是否存在
appService.validPayApp(reqDTO.getAppId()); PayOrderDO order = validatePayOrderCanSubmit(reqVO.getId());
// 校验支付渠道是否有效 // 1.2 校验支付渠道是否有效
PayChannelDO channel = channelService.validPayChannel(reqDTO.getAppId(), reqDTO.getChannelCode()); PayChannelDO channel = validatePayChannelCanSubmit(order.getAppId(), reqVO.getChannelCode());
// 校验支付客户端是否正确初始化
PayClient client = payClientFactory.getPayClient(channel.getId()); PayClient client = payClientFactory.getPayClient(channel.getId());
if (client == null) {
log.error("[submitPayOrder][渠道编号({}) 找不到对应的支付客户端]", channel.getId());
throw exception(ErrorCodeConstants.PAY_CHANNEL_CLIENT_NOT_FOUND);
}
// 获得 PayOrderDO 并校验其是否存在 // 2. 插入 PayOrderExtensionDO
PayOrderDO order = orderMapper.selectById(reqDTO.getId()); PayOrderExtensionDO orderExtension = PayOrderConvert.INSTANCE.convert(reqVO, userIp)
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)
.setOrderId(order.getId()).setNo(generateOrderExtensionNo()) .setOrderId(order.getId()).setNo(generateOrderExtensionNo())
.setChannelId(channel.getId()).setChannelCode(channel.getCode()) .setChannelId(channel.getId()).setChannelCode(channel.getCode())
.setStatus(PayOrderStatusEnum.WAITING.getStatus()); .setStatus(PayOrderStatusEnum.WAITING.getStatus());
orderExtensionMapper.insert(orderExtension); orderExtensionMapper.insert(orderExtension);
// 调用三方接口 // 3. 调用三方接口
PayOrderUnifiedReqDTO unifiedOrderReqDTO = PayOrderConvert.INSTANCE.convert2(reqDTO); PayOrderUnifiedReqDTO unifiedOrderReqDTO = PayOrderConvert.INSTANCE.convert2(reqVO)
// 商户相关字段 // 商户相关的字段
//TODO jason @芋艿 是否加一个属性 如tradeNo 支付订单号 用这个merchantOrderId让人迷糊 .setMerchantOrderId(orderExtension.getNo()) // 注意此处使用的是 PayOrderExtensionDO.no 属性
unifiedOrderReqDTO.setMerchantOrderId(orderExtension.getNo()) // 注意此处使用的是 PayOrderExtensionDO.no 属性
.setSubject(order.getSubject()).setBody(order.getBody()) .setSubject(order.getSubject()).setBody(order.getBody())
.setNotifyUrl(genChannelPayNotifyUrl(channel)) .setNotifyUrl(genChannelPayNotifyUrl(channel))
.setReturnUrl(genChannelReturnUrl(channel)); .setReturnUrl(genChannelReturnUrl(channel))
// 订单相关字段 // 订单相关字段
unifiedOrderReqDTO.setAmount(order.getAmount()).setExpireTime(order.getExpireTime()); .setAmount(order.getAmount()).setExpireTime(order.getExpireTime());
CommonResult<?> unifiedOrderResult = client.unifiedOrder(unifiedOrderReqDTO); PayOrderUnifiedRespDTO unifiedOrderRespDTO = client.unifiedOrder(unifiedOrderReqDTO);
unifiedOrderResult.checkError();
// TODO 轮询三方接口是否已经支付的任务 // TODO 轮询三方接口是否已经支付的任务
// 返回成功 // 返回成功
return new PayOrderSubmitRespDTO().setExtensionId(orderExtension.getId()) return PayOrderConvert.INSTANCE.convert(unifiedOrderRespDTO);
.setInvokeResponse(unifiedOrderResult.getData()); }
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 @Override
@Transactional(rollbackFor = Exception.class) @Transactional(rollbackFor = Exception.class)
public void notifyPayOrder(Long channelId, PayNotifyDataDTO notifyData) { public void notifyPayOrder(Long channelId, PayOrderNotifyRespDTO notify, PayNotifyReqDTO rawNotify) {
// TODO 芋艿记录回调日志
log.info("[notifyPayOrder][channelId({}) 回调数据({})]", channelId, notifyData.getBody());
// 校验支付渠道是否有效 // 校验支付渠道是否有效
PayChannelDO channel = channelService.validPayChannel(channelId); PayChannelDO channel = channelService.validPayChannel(channelId);
TenantUtils.execute(channel.getTenantId(), () -> { TenantUtils.execute(channel.getTenantId(), () -> {
try { // 1. 更新 PayOrderExtensionDO 支付成功
notifyPayOrder(channel, notifyData); PayOrderExtensionDO orderExtension = updatePayOrderExtensionSuccess(notify.getOrderExtensionNo(),
} catch (Exception e) { rawNotify);
throw new RuntimeException(e); // 2. 更新 PayOrderDO 支付成功
} PayOrderDO order = updatePayOrderSuccess(channel, orderExtension, notify);
// 3. 插入支付通知记录
notifyService.createPayNotifyTask(PayNotifyTaskCreateReqDTO.builder()
.type(PayNotifyTypeEnum.ORDER.getType()).dataId(order.getId()).build());
}); });
} }
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());
// 2. 更新 PayOrderDO 支付成功
PayOrderDO order = updatePayOrderSuccess(channel, orderExtension, notifyRespDTO);
// 3. 插入支付通知记录
notifyService.createPayNotifyTask(PayNotifyTaskCreateReqDTO.builder()
.type(PayNotifyTypeEnum.ORDER.getType()).dataId(order.getId()).build());
}
/** /**
* 更新 PayOrderExtensionDO 支付成功 * 更新 PayOrderExtensionDO 支付成功
* *
* @param no 支付订单号支付模块 * @param no 支付订单号支付模块
* @param body 回调内容 * @param rawNotify 通知数据
* @return PayOrderExtensionDO 对象 * @return PayOrderExtensionDO 对象
*/ */
private PayOrderExtensionDO updatePayOrderExtensionSuccess(String no, String body) { private PayOrderExtensionDO updatePayOrderExtensionSuccess(String no, PayNotifyReqDTO rawNotify) {
// 1.1 查询 PayOrderExtensionDO // 1.1 查询 PayOrderExtensionDO
PayOrderExtensionDO orderExtension = orderExtensionMapper.selectByNo(no); PayOrderExtensionDO orderExtension = orderExtensionMapper.selectByNo(no);
if (orderExtension == null) { if (orderExtension == null) {
@ -267,7 +257,8 @@ public class PayOrderServiceImpl implements PayOrderService {
// 1.2 更新 PayOrderExtensionDO // 1.2 更新 PayOrderExtensionDO
int updateCounts = orderExtensionMapper.updateByIdAndStatus(orderExtension.getId(), int updateCounts = orderExtensionMapper.updateByIdAndStatus(orderExtension.getId(),
PayOrderStatusEnum.WAITING.getStatus(), PayOrderExtensionDO.builder().id(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) { // 校验状态必须是待支付 if (updateCounts == 0) { // 校验状态必须是待支付
throw exception(ErrorCodeConstants.PAY_ORDER_EXTENSION_STATUS_IS_NOT_WAITING); throw exception(ErrorCodeConstants.PAY_ORDER_EXTENSION_STATUS_IS_NOT_WAITING);
} }
@ -280,11 +271,11 @@ public class PayOrderServiceImpl implements PayOrderService {
* *
* @param channel 支付渠道 * @param channel 支付渠道
* @param orderExtension 支付拓展单 * @param orderExtension 支付拓展单
* @param notifyRespDTO 通知回调 * @param notify 通知回调
* @return PayOrderDO 对象 * @return PayOrderDO 对象
*/ */
private PayOrderDO updatePayOrderSuccess(PayChannelDO channel, PayOrderExtensionDO orderExtension, private PayOrderDO updatePayOrderSuccess(PayChannelDO channel, PayOrderExtensionDO orderExtension,
PayOrderNotifyRespDTO notifyRespDTO) { PayOrderNotifyRespDTO notify) {
// 2.1 判断 PayOrderDO 是否处于待支付 // 2.1 判断 PayOrderDO 是否处于待支付
PayOrderDO order = orderMapper.selectById(orderExtension.getOrderId()); PayOrderDO order = orderMapper.selectById(orderExtension.getOrderId());
if (order == null) { if (order == null) {
@ -297,8 +288,8 @@ public class PayOrderServiceImpl implements PayOrderService {
int updateCounts = orderMapper.updateByIdAndStatus(order.getId(), PayOrderStatusEnum.WAITING.getStatus(), int updateCounts = orderMapper.updateByIdAndStatus(order.getId(), PayOrderStatusEnum.WAITING.getStatus(),
PayOrderDO.builder().status(PayOrderStatusEnum.SUCCESS.getStatus()) PayOrderDO.builder().status(PayOrderStatusEnum.SUCCESS.getStatus())
.channelId(channel.getId()).channelCode(channel.getCode()) .channelId(channel.getId()).channelCode(channel.getCode())
.successTime(notifyRespDTO.getSuccessTime()).successExtensionId(orderExtension.getId()) .successTime(notify.getSuccessTime()).successExtensionId(orderExtension.getId())
.channelOrderNo(notifyRespDTO.getChannelOrderNo()).channelUserId(notifyRespDTO.getChannelUserId()) .channelOrderNo(notify.getChannelOrderNo()).channelUserId(notify.getChannelUserId())
.notifyTime(LocalDateTime.now()).build()); .notifyTime(LocalDateTime.now()).build());
if (updateCounts == 0) { // 校验状态必须是待支付 if (updateCounts == 0) { // 校验状态必须是待支付
throw exception(ErrorCodeConstants.PAY_ORDER_STATUS_IS_NOT_WAITING); throw exception(ErrorCodeConstants.PAY_ORDER_STATUS_IS_NOT_WAITING);

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -1,12 +1,12 @@
package cn.iocoder.yudao.module.pay.service.refund; 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.PayRefundExportReqVO;
import cn.iocoder.yudao.module.pay.controller.admin.refund.vo.PayRefundPageReqVO; import cn.iocoder.yudao.module.pay.controller.admin.refund.vo.PayRefundPageReqVO;
import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.module.pay.dal.dataobject.refund.PayRefundDO; 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; import java.util.List;
@ -42,20 +42,20 @@ public interface PayRefundService {
List<PayRefundDO> getRefundList(PayRefundExportReqVO exportReqVO); List<PayRefundDO> getRefundList(PayRefundExportReqVO exportReqVO);
/** /**
* 提交退款申请 * 创建退款申请
* *
* @param reqDTO 退款申请信息 * @param reqDTO 退款申请信息
* @return 退款申请返回信息 * @return 退款单号
*/ */
PayRefundRespDTO submitRefundOrder(PayRefundReqDTO reqDTO); Long createPayRefund(PayRefundCreateReqDTO reqDTO);
/** /**
* 渠道的退款通知 * 渠道的退款通知
* *
* @param channelId 渠道编号 * @param channelId 渠道编号
* @param notifyData 通知数据 * @param notify 通知
* @throws Exception 退款通知异常 * @param rawNotify 通知数据
*/ */
void notifyPayRefund(Long channelId, PayNotifyDataDTO notifyData) throws Exception; void notifyPayRefund(Long channelId, PayRefundNotifyRespDTO notify, PayNotifyReqDTO rawNotify);
} }

View File

@ -1,15 +1,17 @@
package cn.iocoder.yudao.module.pay.service.refund; package cn.iocoder.yudao.module.pay.service.refund;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.core.util.StrUtil; import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil; 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.PayClient;
import cn.iocoder.yudao.framework.pay.core.client.PayClientFactory; 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.notify.PayNotifyReqDTO;
import cn.iocoder.yudao.framework.pay.core.client.dto.PayNotifyDataDTO; import cn.iocoder.yudao.framework.pay.core.client.dto.notify.PayRefundNotifyRespDTO;
import cn.iocoder.yudao.framework.pay.core.client.dto.PayRefundNotifyDTO; import cn.iocoder.yudao.framework.pay.core.client.dto.refund.PayRefundUnifiedReqDTO;
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.enums.PayNotifyRefundStatusEnum; 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.PayRefundExportReqVO;
import cn.iocoder.yudao.module.pay.controller.admin.refund.vo.PayRefundPageReqVO; import cn.iocoder.yudao.module.pay.controller.admin.refund.vo.PayRefundPageReqVO;
import cn.iocoder.yudao.module.pay.dal.dataobject.merchant.PayAppDO; 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.dataobject.refund.PayRefundDO;
import cn.iocoder.yudao.module.pay.dal.mysql.order.PayOrderMapper; import cn.iocoder.yudao.module.pay.dal.mysql.order.PayOrderMapper;
import cn.iocoder.yudao.module.pay.dal.mysql.refund.PayRefundMapper; 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.ErrorCodeConstants;
import cn.iocoder.yudao.module.pay.enums.notify.PayNotifyTypeEnum; import cn.iocoder.yudao.module.pay.enums.notify.PayNotifyTypeEnum;
import cn.iocoder.yudao.module.pay.enums.order.PayOrderNotifyStatusEnum; 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.notify.dto.PayNotifyTaskCreateReqDTO;
import cn.iocoder.yudao.module.pay.service.order.PayOrderExtensionService; 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.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 lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
@ -54,6 +53,9 @@ import java.util.Objects;
@Validated @Validated
public class PayRefundServiceImpl implements PayRefundService { public class PayRefundServiceImpl implements PayRefundService {
@Resource
private PayProperties payProperties;
@Resource @Resource
private PayClientFactory payClientFactory; private PayClientFactory payClientFactory;
@ -90,9 +92,9 @@ public class PayRefundServiceImpl implements PayRefundService {
@Override @Override
@Transactional(rollbackFor = Exception.class) @Transactional(rollbackFor = Exception.class)
public PayRefundRespDTO submitRefundOrder(PayRefundReqDTO req) { public Long createPayRefund(PayRefundCreateReqDTO reqDTO) {
// 获得 PayOrderDO // 获得 PayOrderDO
PayOrderDO order = orderService.getOrder(req.getPayOrderId()); PayOrderDO order = orderService.getOrder(reqDTO.getPayOrderId());
// 校验订单是否存在 // 校验订单是否存在
if (Objects.isNull(order) ) { if (Objects.isNull(order) ) {
throw ServiceExceptionUtil.exception(ErrorCodeConstants.PAY_ORDER_NOT_FOUND); 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); 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; PayRefundTypeEnum refundType = PayRefundTypeEnum.SOME;
if (Objects.equals(req.getAmount(), order.getAmount())) { if (Objects.equals(reqDTO.getAmount(), order.getAmount())) {
refundType = PayRefundTypeEnum.ALL; refundType = PayRefundTypeEnum.ALL;
} }
PayOrderExtensionDO orderExtensionDO = orderExtensionService.getOrderExtension(order.getSuccessExtensionId()); 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)){ if(Objects.nonNull(payRefundDO)){
// 退款订单已经提交过 // 退款订单已经提交过
//TODO 校验相同退款单的金额 //TODO 校验相同退款单的金额
@ -137,15 +143,15 @@ public class PayRefundServiceImpl implements PayRefundService {
.channelId(order.getChannelId()) .channelId(order.getChannelId())
.merchantId(order.getMerchantId()) .merchantId(order.getMerchantId())
.orderId(order.getId()) .orderId(order.getId())
.merchantRefundNo(req.getMerchantRefundId()) .merchantRefundNo(merchantRefundId) // TODO 芋艿需要优化
.notifyUrl(app.getRefundNotifyUrl()) .notifyUrl(app.getRefundNotifyUrl())
.payAmount(order.getAmount()) .payAmount(order.getAmount())
.refundAmount(req.getAmount()) .refundAmount(reqDTO.getAmount())
.userIp(req.getUserIp()) .userIp(reqDTO.getUserIp())
.merchantOrderId(order.getMerchantOrderId()) .merchantOrderId(order.getMerchantOrderId())
.tradeNo(orderExtensionDO.getNo()) .tradeNo(orderExtensionDO.getNo())
.status(PayRefundStatusEnum.CREATE.getStatus()) .status(PayRefundStatusEnum.CREATE.getStatus())
.reason(req.getReason()) .reason(reqDTO.getReason())
.notifyStatus(PayOrderNotifyStatusEnum.NO.getStatus()) .notifyStatus(PayOrderNotifyStatusEnum.NO.getStatus())
.type(refundType.getStatus()) .type(refundType.getStatus())
.build(); .build();
@ -153,47 +159,50 @@ public class PayRefundServiceImpl implements PayRefundService {
} }
// TODO @jason搞到 convert 一些额外的自动手动 set // TODO @jason搞到 convert 一些额外的自动手动 set
PayRefundUnifiedReqDTO unifiedReqDTO = new PayRefundUnifiedReqDTO(); PayRefundUnifiedReqDTO unifiedReqDTO = new PayRefundUnifiedReqDTO();
unifiedReqDTO.setUserIp(req.getUserIp()) unifiedReqDTO.setUserIp(reqDTO.getUserIp())
.setAmount(req.getAmount()) .setAmount(reqDTO.getAmount())
.setChannelOrderNo(order.getChannelOrderNo()) .setChannelOrderNo(order.getChannelOrderNo())
.setPayTradeNo(orderExtensionDO.getNo()) .setPayTradeNo(orderExtensionDO.getNo())
.setMerchantRefundId(req.getMerchantRefundId()) .setMerchantRefundId(merchantRefundId) // TODO 芋艿需要优化
.setReason(req.getReason()); .setNotifyUrl(genChannelPayNotifyUrl(channel)) // TODO 芋艿优化下 notifyUrl
.setReason(reqDTO.getReason());
// 向渠道发起退款申请 // 向渠道发起退款申请
PayCommonResult<PayRefundUnifiedRespDTO> refundUnifiedResult = client.unifiedRefund(unifiedReqDTO); client.unifiedRefund(unifiedReqDTO);
// 检查是否失败失败抛出业务异常 // 检查是否失败失败抛出业务异常
// TODO 渠道的异常记录 // TODO 渠道的异常记录
// TODO @jason可以先打个 warn log // 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 @Override
@Transactional(rollbackFor = Exception.class) @Transactional(rollbackFor = Exception.class)
public void notifyPayRefund(Long channelId, PayNotifyDataDTO notifyData) { public void notifyPayRefund(Long channelId, PayRefundNotifyRespDTO notify, PayNotifyReqDTO rawNotify) {
log.info("[notifyPayRefund][channelId({}) 回调数据({})]", channelId, notifyData.getBody());
// 校验支付渠道是否有效 // 校验支付渠道是否有效
// TODO 芋艿需要重构下这块的逻辑
PayChannelDO channel = channelService.validPayChannel(channelId); PayChannelDO channel = channelService.validPayChannel(channelId);
// 校验支付客户端是否正确初始化 if (Objects.equals(PayNotifyRefundStatusEnum.SUCCESS, notify.getStatus())){
PayClient client = payClientFactory.getPayClient(channel.getId()); payRefundSuccess(notify);
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);
} else { } else {
//TODO 支付异常 支付宝似乎没有支付异常的通知 //TODO 支付异常 支付宝似乎没有支付异常的通知
// TODO @jason那这里可以考虑打个 error logger @芋艿 微信是否存在支付异常通知 // 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) { if (refundDO == null) {
log.error("[payRefundSuccess][不存在 seqNo 为{} 的支付退款单]", refundNotify.getReqNo()); log.error("[payRefundSuccess][不存在 seqNo 为{} 的支付退款单]", refundNotify.getReqNo());
throw ServiceExceptionUtil.exception(ErrorCodeConstants.PAY_REFUND_NOT_FOUND); throw ServiceExceptionUtil.exception(ErrorCodeConstants.PAY_REFUND_NOT_FOUND);
@ -235,10 +244,11 @@ public class PayRefundServiceImpl implements PayRefundService {
/** /**
* 校验是否进行退款 * 校验是否进行退款
* @param req 退款申请信息 *
* @param reqDTO 退款申请信息
* @param order 原始支付订单信息 * @param order 原始支付订单信息
*/ */
private void validatePayRefund(PayRefundReqDTO req, PayOrderDO order) { private void validatePayRefund(PayRefundCreateReqDTO reqDTO, PayOrderDO order) {
// 校验状态必须是支付状态 // 校验状态必须是支付状态
if (!PayOrderStatusEnum.SUCCESS.getStatus().equals(order.getStatus())) { if (!PayOrderStatusEnum.SUCCESS.getStatus().equals(order.getStatus())) {
throw ServiceExceptionUtil.exception(ErrorCodeConstants.PAY_ORDER_STATUS_IS_NOT_SUCCESS); 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); 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); throw ServiceExceptionUtil.exception(ErrorCodeConstants.PAY_REFUND_AMOUNT_EXCEED);
} }
// 校验渠道订单号 // 校验渠道订单号

View File

@ -18,6 +18,7 @@ public class PaySeqUtils {
private static final AtomicLong MER_ORDER_NO_SEQ = new AtomicLong(0L); private static final AtomicLong MER_ORDER_NO_SEQ = new AtomicLong(0L);
// TODO 芋艿需要看看
/** /**
* 生成商户退款单号用于测试应该由商户系统生成 * 生成商户退款单号用于测试应该由商户系统生成
* @return 商户退款单 * @return 商户退款单
@ -28,6 +29,8 @@ public class PaySeqUtils {
(int) MER_REFUND_NO_SEQ.getAndIncrement() % 10000); (int) MER_REFUND_NO_SEQ.getAndIncrement() % 10000);
} }
// TODO 芋艿需要看看
/** /**
* 生成退款请求号 * 生成退款请求号
* @return 退款请求号 * @return 退款请求号

View File

@ -54,11 +54,11 @@
<!-- <version>${revision}</version>--> <!-- <version>${revision}</version>-->
<!-- </dependency>--> <!-- </dependency>-->
<!-- 支付服务 --> <!-- 支付服务 -->
<dependency> <!-- <dependency>-->
<groupId>cn.iocoder.boot</groupId> <!-- <groupId>cn.iocoder.boot</groupId>-->
<artifactId>yudao-module-pay-biz</artifactId> <!-- <artifactId>yudao-module-pay-biz</artifactId>-->
<version>${revision}</version> <!-- <version>${revision}</version>-->
</dependency> <!-- </dependency>-->
<!-- 微信公众号模块。默认注释,保证编译速度 --> <!-- 微信公众号模块。默认注释,保证编译速度 -->
<!-- <dependency>--> <!-- <dependency>-->

View File

@ -1,4 +0,0 @@
/**
* 占位
*/
package cn.iocoder.yudao.module.shop.controller.admin;

View File

@ -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);
}
}

View File

@ -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;
}

View File

@ -1,9 +0,0 @@
/**
* shop 包下我们放商城业务
* 例如说商品订单等等
* 注意目前仅仅作为 demo 演示对接 pay 支付系统
*
* 缩写shop
*/
// TODO 芋艿后续会迁移到 yudao-module-mall-trade
package cn.iocoder.yudao.module.shop;

View File

@ -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
}

View File

@ -153,6 +153,7 @@ logging:
cn.iocoder.yudao.module.infra.dal.mysql: debug cn.iocoder.yudao.module.infra.dal.mysql: debug
cn.iocoder.yudao.module.infra.dal.mysql.job.JobLogMapper: INFO # 配置 JobLogMapper 的日志级别为 info 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: 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.system.dal.mysql: debug
cn.iocoder.yudao.module.tool.dal.mysql: debug cn.iocoder.yudao.module.tool.dal.mysql: debug
cn.iocoder.yudao.module.member.dal.mysql: debug cn.iocoder.yudao.module.member.dal.mysql: debug

View File

@ -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>

View File

@ -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>

File diff suppressed because one or more lines are too long

View File

@ -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>

View File

@ -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>

View File

@ -57,6 +57,7 @@
"jsencrypt": "3.3.1", "jsencrypt": "3.3.1",
"min-dash": "3.5.2", "min-dash": "3.5.2",
"nprogress": "0.2.0", "nprogress": "0.2.0",
"qrcode.vue": "^1.7.0",
"quill": "1.3.7", "quill": "1.3.7",
"screenfull": "5.0.2", "screenfull": "5.0.2",
"sortablejs": "1.10.2", "sortablejs": "1.10.2",

View File

@ -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'
})
}

View File

@ -1,23 +1,5 @@
import request from '@/utils/request' 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) { export function deleteOrder(id) {
return request({ 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) { export function getOrderPage(query) {
return request({ return request({

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -221,6 +221,21 @@ export const constantRoutes = [
component: (resolve) => require(['@/views/mall/trade/order/detail'], resolve) 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)
}]
} }
] ]

View File

@ -148,6 +148,28 @@ export const PayChannelEnum = {
"code": "alipay_qr", "code": "alipay_qr",
"name": "支付宝扫码支付" "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: { CLOSED: {
status: 20, status: 20,
name: '支付' name: '支付关闭'
} }
} }

View File

@ -53,11 +53,6 @@
</template> </template>
</el-table-column> </el-table-column>
<el-table-column label="商户名称" align="center" prop="payMerchant.name"/> <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="支付宝配置" align="center">
<el-table-column :label="payChannelEnum.ALIPAY_APP.name" align="center"> <el-table-column :label="payChannelEnum.ALIPAY_APP.name" align="center">
<template v-slot="scope"> <template v-slot="scope">
@ -107,6 +102,18 @@
</el-button> </el-button>
</template> </template>
</el-table-column> </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>
<el-table-column label="微信配置" align="center"> <el-table-column label="微信配置" align="center">
<el-table-column :label="payChannelEnum.WX_LITE.name" align="center"> <el-table-column :label="payChannelEnum.WX_LITE.name" align="center">

View File

@ -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>

View File

@ -215,7 +215,7 @@
</template> </template>
<script> <script>
import {getOrder, getOrderPage, exportOrderExcel} from "@/api/pay/order"; import { getOrderDetail, getOrderPage, exportOrderExcel} from "@/api/pay/order";
import {getMerchantListByName} from "@/api/pay/merchant"; import {getMerchantListByName} from "@/api/pay/merchant";
import {getAppListByMerchantId} from "@/api/pay/app"; import {getAppListByMerchantId} from "@/api/pay/app";
import {DICT_TYPE, getDictDatas} from "@/utils/dict"; import {DICT_TYPE, getDictDatas} from "@/utils/dict";
@ -250,7 +250,7 @@ const defaultOrderDetail = {
}; };
export default { export default {
name: "Order", name: "PayOrder",
components: {}, components: {},
data() { data() {
return { return {
@ -364,7 +364,7 @@ export default {
*/ */
handleQueryDetails(row) { handleQueryDetails(row) {
this.orderDetail = JSON.parse(JSON.stringify(defaultOrderDetail)); this.orderDetail = JSON.parse(JSON.stringify(defaultOrderDetail));
getOrder(row.id).then(response => { getOrderDetail(row.id).then(response => {
this.orderDetail = response.data; this.orderDetail = response.data;
if (response.data.payOrderExtension === null) { if (response.data.payOrderExtension === null) {
this.orderDetail.payOrderExtension = Object.assign(defaultOrderDetail.payOrderExtension, {}); this.orderDetail.payOrderExtension = Object.assign(defaultOrderDetail.payOrderExtension, {});

View File

@ -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>

File diff suppressed because it is too large Load Diff