From 7c33311efba3457099ee72639ef1ab54bddd10e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=B5=B7=E8=A8=80?= <2439534736@qq.com> Date: Wed, 22 Oct 2025 17:41:38 +0800 Subject: [PATCH] =?UTF-8?q?1.=E9=9B=86=E6=88=90=E6=94=AF=E4=BB=98=E5=AE=9D?= =?UTF-8?q?=E6=B2=99=E7=AE=B1=E6=94=AF=E4=BB=98=E5=8A=9F=E8=83=BD=202.?= =?UTF-8?q?=E8=AE=A2=E5=8D=95=E4=B8=8B=E5=8D=95=203.=E5=9B=9E=E8=B0=83?= =?UTF-8?q?=E9=80=9A=E7=9F=A5=EF=BC=88=E6=9C=AA=E6=88=90=E5=8A=9F=EF=BC=89?= =?UTF-8?q?=204.=E5=9B=9E=E6=9F=A5=E8=AE=A2=E5=8D=95=E6=94=AF=E4=BB=98?= =?UTF-8?q?=E7=8A=B6=E6=80=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/alipay.html | 5 + pom.xml | 11 +- .../xf/basedemo/config/pay/AliPayConfig.java | 35 ++++ .../config/pay/AliPayConfigProperties.java | 52 ++++++ .../business/PayCallbackController.java | 36 +++++ .../business/PayOrderController.java | 45 ++++++ .../controller/business/UserController.java | 4 +- .../interceptor/InterceptorConfig.java | 2 +- .../interceptor/TokenInterceptor.java | 4 +- .../LoginInfoReq.java} | 4 +- .../xf/basedemo/model/req/PayOrderFrom.java | 31 ++++ .../cn/xf/basedemo/model/res/PayOrderRes.java | 21 +++ .../cn/xf/basedemo/service/OrderService.java | 15 ++ .../cn/xf/basedemo/service/UserService.java | 4 +- .../service/impl/OrderServiceImpl.java | 153 ++++++++++++++++++ .../service/impl/UserServiceImpl.java | 5 +- 16 files changed, 414 insertions(+), 13 deletions(-) create mode 100644 docs/alipay.html create mode 100644 src/main/java/cn/xf/basedemo/config/pay/AliPayConfig.java create mode 100644 src/main/java/cn/xf/basedemo/config/pay/AliPayConfigProperties.java create mode 100644 src/main/java/cn/xf/basedemo/controller/business/PayCallbackController.java create mode 100644 src/main/java/cn/xf/basedemo/controller/business/PayOrderController.java rename src/main/java/cn/xf/basedemo/model/{res/LoginInfoRes.java => req/LoginInfoReq.java} (83%) create mode 100644 src/main/java/cn/xf/basedemo/model/req/PayOrderFrom.java create mode 100644 src/main/java/cn/xf/basedemo/model/res/PayOrderRes.java create mode 100644 src/main/java/cn/xf/basedemo/service/OrderService.java create mode 100644 src/main/java/cn/xf/basedemo/service/impl/OrderServiceImpl.java diff --git a/docs/alipay.html b/docs/alipay.html new file mode 100644 index 0000000..05aa339 --- /dev/null +++ b/docs/alipay.html @@ -0,0 +1,5 @@ +
+ + +
+ diff --git a/pom.xml b/pom.xml index ebcfba7..d667951 100644 --- a/pom.xml +++ b/pom.xml @@ -27,6 +27,8 @@ 2023.0.1 8.16.0 4.0.1 + 2.3.4 + 4.40.476.ALL @@ -132,7 +134,14 @@ org.apache.rocketmq rocketmq-spring-boot-starter - 2.3.4 + ${rocketmq.version} + + + + + com.alipay.sdk + alipay-sdk-java + ${alipay.sdk.version} diff --git a/src/main/java/cn/xf/basedemo/config/pay/AliPayConfig.java b/src/main/java/cn/xf/basedemo/config/pay/AliPayConfig.java new file mode 100644 index 0000000..c75eafc --- /dev/null +++ b/src/main/java/cn/xf/basedemo/config/pay/AliPayConfig.java @@ -0,0 +1,35 @@ +package cn.xf.basedemo.config.pay; + + +import com.alipay.api.AlipayClient; +import com.alipay.api.DefaultAlipayClient; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * AliPayConfig + * + * @author 海言 + * @date 2025/10/22 + * @time 13:47 + * @Description 支付宝支付配置类 + */ +@Configuration +@EnableConfigurationProperties(AliPayConfigProperties.class) +public class AliPayConfig { + + @Bean(name = "alipayClient") + @ConditionalOnProperty(value = "pay.ali.enabled", havingValue = "true", matchIfMissing = false) + public AlipayClient alipayClient(AliPayConfigProperties properties){ + return new DefaultAlipayClient( + properties.getGatewayUrl(), + properties.getAppId(), + properties.getMerchantPrivateKey(), + properties.getFormat(), + properties.getCharset(), + properties.getAlipayPublicKey(), + properties.getSign_type()); + } +} diff --git a/src/main/java/cn/xf/basedemo/config/pay/AliPayConfigProperties.java b/src/main/java/cn/xf/basedemo/config/pay/AliPayConfigProperties.java new file mode 100644 index 0000000..b6d5624 --- /dev/null +++ b/src/main/java/cn/xf/basedemo/config/pay/AliPayConfigProperties.java @@ -0,0 +1,52 @@ +package cn.xf.basedemo.config.pay; + + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Configuration; + +/** + * AliPayConfig + * + * @author 海言 + * @date 2025/10/22 + * @time 13:35 + * @Description 支付宝支付配置常量 + */ +@Data +@ConfigurationProperties(prefix = "pay.ali") +public class AliPayConfigProperties { + + /** + * 应用ID,您的APPID,收款账号既是您的APPID对应支付宝账号 + */ + private String appId; + + /** + * 商户PID + */ + private String pId; + + /** + * 商家私钥 + */ + private String merchantPrivateKey; + + /** + * 支付宝公钥 + */ + private String alipayPublicKey; + + /** + * 支付宝沙箱网关 + */ + private String gatewayUrl; + + // 签名方式 + private String sign_type = "RSA2"; + // 字符编码格式 + private String charset = "GBK"; + // 传输格式 + private String format = "json"; + +} diff --git a/src/main/java/cn/xf/basedemo/controller/business/PayCallbackController.java b/src/main/java/cn/xf/basedemo/controller/business/PayCallbackController.java new file mode 100644 index 0000000..dfc3f91 --- /dev/null +++ b/src/main/java/cn/xf/basedemo/controller/business/PayCallbackController.java @@ -0,0 +1,36 @@ +package cn.xf.basedemo.controller.business; + + +import cn.xf.basedemo.service.OrderService; +import io.swagger.v3.oas.annotations.Operation; +import jakarta.annotation.Resource; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import javax.servlet.http.HttpServletRequest; + +/** + * PayCallbackController + * + * @author 海言 + * @date 2025/10/22 + * @time 16:16 + * @Description 支付回调控制器 + */ +@RestController +@RequestMapping("/pay/callback") +public class PayCallbackController { + + @Resource + private OrderService userService; + + //回调 + @Operation(summary = "支付宝支付回调", description = "支付宝支付回调") + @PostMapping("/ali/notice") + public String aliCallback(HttpServletRequest request) { + userService.aliCallback(request); + return "success"; + } +} diff --git a/src/main/java/cn/xf/basedemo/controller/business/PayOrderController.java b/src/main/java/cn/xf/basedemo/controller/business/PayOrderController.java new file mode 100644 index 0000000..5b9775c --- /dev/null +++ b/src/main/java/cn/xf/basedemo/controller/business/PayOrderController.java @@ -0,0 +1,45 @@ +package cn.xf.basedemo.controller.business; + + +import cn.xf.basedemo.common.model.RetObj; +import cn.xf.basedemo.model.req.PayOrderFrom; +import cn.xf.basedemo.service.OrderService; +import io.swagger.v3.oas.annotations.Operation; +import jakarta.annotation.Resource; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.*; + +import javax.servlet.http.HttpServletRequest; + +/** + * PayOrderController + * + * @author 海言 + * @date 2025/10/22 + * @time 14:07 + * @Description 支付下单控制器 + */ +@RestController +@RequestMapping("/pay") +public class PayOrderController { + + @Resource + private OrderService orderService; + + @Operation(summary = "支付宝支付下单", description = "支付宝支付下单") + @PostMapping("/ali/createOrder") + public RetObj aliCreateOrder(@RequestBody PayOrderFrom from) { + + return orderService.aliCreateOrder(from); + } + + //掉单查询支付宝支付订单状态 + @Operation(summary = "掉单查询支付宝支付订单状态", description = "掉单查询支付宝支付订单状态") + @GetMapping("/ali/queryOrderStatus") + public String queryAlipayOrderStatus(String orderNo) { + return orderService.queryAlipayOrderStatus(orderNo); + } + + +} + diff --git a/src/main/java/cn/xf/basedemo/controller/business/UserController.java b/src/main/java/cn/xf/basedemo/controller/business/UserController.java index 22598ad..543c84d 100644 --- a/src/main/java/cn/xf/basedemo/controller/business/UserController.java +++ b/src/main/java/cn/xf/basedemo/controller/business/UserController.java @@ -3,7 +3,7 @@ package cn.xf.basedemo.controller.business; import cn.xf.basedemo.common.model.LoginUser; import cn.xf.basedemo.common.model.RetObj; import cn.xf.basedemo.interceptor.SessionContext; -import cn.xf.basedemo.model.res.LoginInfoRes; +import cn.xf.basedemo.model.req.LoginInfoReq; import cn.xf.basedemo.service.UserService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; @@ -27,7 +27,7 @@ public class UserController { @Operation(summary = "用户登录", description = "用户登录") @PostMapping("/login") - public RetObj login(@RequestBody LoginInfoRes res) { + public RetObj login(@RequestBody LoginInfoReq res) { return userService.login(res); } diff --git a/src/main/java/cn/xf/basedemo/interceptor/InterceptorConfig.java b/src/main/java/cn/xf/basedemo/interceptor/InterceptorConfig.java index 08bdc00..c2dbbf5 100644 --- a/src/main/java/cn/xf/basedemo/interceptor/InterceptorConfig.java +++ b/src/main/java/cn/xf/basedemo/interceptor/InterceptorConfig.java @@ -25,7 +25,7 @@ public class InterceptorConfig implements WebMvcConfigurer { public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(tokenInterceptor()) //登录逻辑拦截类 .addPathPatterns("/**") //需要拦截的请求(设置的全部拦截) - .excludePathPatterns("/user/login", "/web/**"); //忽略的请求 + .excludePathPatterns("/user/login", "/web/**", "/pay/callback/**"); //忽略的请求 } diff --git a/src/main/java/cn/xf/basedemo/interceptor/TokenInterceptor.java b/src/main/java/cn/xf/basedemo/interceptor/TokenInterceptor.java index 98daf8b..cfc18c6 100644 --- a/src/main/java/cn/xf/basedemo/interceptor/TokenInterceptor.java +++ b/src/main/java/cn/xf/basedemo/interceptor/TokenInterceptor.java @@ -32,7 +32,7 @@ public class TokenInterceptor implements HandlerInterceptor { //不拦截的请求列表 - private static final List EXCLUDE_PATH_LIST = Arrays.asList("/user/login", "/web/login","/swagger-ui.html","/v3/api-docs","/swagger-ui/index.html"); + private static final List EXCLUDE_PATH_LIST = Arrays.asList("/user/login", "/web/login", "/swagger-ui.html", "/v3/api-docs", "/swagger-ui/index.html"); @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { @@ -49,7 +49,7 @@ public class TokenInterceptor implements HandlerInterceptor { token = request.getParameter("token"); if (StringUtils.isEmpty(token)) { throw new LoginException("请先登录"); - }else { + } else { //验证token if (!token.startsWith("Bearer ")) { throw new LoginException(ResponseCode.USER_INPUT_ERROR); diff --git a/src/main/java/cn/xf/basedemo/model/res/LoginInfoRes.java b/src/main/java/cn/xf/basedemo/model/req/LoginInfoReq.java similarity index 83% rename from src/main/java/cn/xf/basedemo/model/res/LoginInfoRes.java rename to src/main/java/cn/xf/basedemo/model/req/LoginInfoReq.java index 7c7a5d0..be4c509 100644 --- a/src/main/java/cn/xf/basedemo/model/res/LoginInfoRes.java +++ b/src/main/java/cn/xf/basedemo/model/req/LoginInfoReq.java @@ -1,4 +1,4 @@ -package cn.xf.basedemo.model.res; +package cn.xf.basedemo.model.req; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; @@ -11,7 +11,7 @@ import lombok.Data; * @create: 2022-07-04 11:46 **/ @Data -public class LoginInfoRes { +public class LoginInfoReq { /** * 登录密文 diff --git a/src/main/java/cn/xf/basedemo/model/req/PayOrderFrom.java b/src/main/java/cn/xf/basedemo/model/req/PayOrderFrom.java new file mode 100644 index 0000000..00da216 --- /dev/null +++ b/src/main/java/cn/xf/basedemo/model/req/PayOrderFrom.java @@ -0,0 +1,31 @@ +package cn.xf.basedemo.model.req; + + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.math.BigDecimal; + +/** + * PayOrderFrom + * + * @author 海言 + * @date 2025/10/22 + * @time 14:09 + * @Description + */ +@Data +public class PayOrderFrom { + + @Schema(name = "商品id") + private Long productId; + + @Schema(name = "商品名称") + private String productName; + + @Schema(name = "商品价格") + private BigDecimal price; + + @Schema(name = "商品数量") + private Integer num; +} diff --git a/src/main/java/cn/xf/basedemo/model/res/PayOrderRes.java b/src/main/java/cn/xf/basedemo/model/res/PayOrderRes.java new file mode 100644 index 0000000..de32066 --- /dev/null +++ b/src/main/java/cn/xf/basedemo/model/res/PayOrderRes.java @@ -0,0 +1,21 @@ +package cn.xf.basedemo.model.res; + + +import lombok.Data; + +/** + * PayOrderRes + * + * @author 海言 + * @date 2025/10/22 + * @time 14:12 + * @Description + */ +@Data +public class PayOrderRes { + + private String orderNo; + + private String from; + +} diff --git a/src/main/java/cn/xf/basedemo/service/OrderService.java b/src/main/java/cn/xf/basedemo/service/OrderService.java new file mode 100644 index 0000000..c5a1696 --- /dev/null +++ b/src/main/java/cn/xf/basedemo/service/OrderService.java @@ -0,0 +1,15 @@ +package cn.xf.basedemo.service; + +import cn.xf.basedemo.common.model.RetObj; +import cn.xf.basedemo.model.req.PayOrderFrom; + +import javax.servlet.http.HttpServletRequest; + +public interface OrderService { + + RetObj aliCreateOrder(PayOrderFrom from); + + String aliCallback(HttpServletRequest request); + + String queryAlipayOrderStatus(String orderNo); +} diff --git a/src/main/java/cn/xf/basedemo/service/UserService.java b/src/main/java/cn/xf/basedemo/service/UserService.java index bf66f8f..2cbe5d1 100644 --- a/src/main/java/cn/xf/basedemo/service/UserService.java +++ b/src/main/java/cn/xf/basedemo/service/UserService.java @@ -1,7 +1,7 @@ package cn.xf.basedemo.service; import cn.xf.basedemo.common.model.RetObj; -import cn.xf.basedemo.model.res.LoginInfoRes; +import cn.xf.basedemo.model.req.LoginInfoReq; /** * @program: xf-boot-base @@ -12,7 +12,7 @@ import cn.xf.basedemo.model.res.LoginInfoRes; **/ public interface UserService { - RetObj login(LoginInfoRes res); + RetObj login(LoginInfoReq res); RetObj syncEs(Long userId); diff --git a/src/main/java/cn/xf/basedemo/service/impl/OrderServiceImpl.java b/src/main/java/cn/xf/basedemo/service/impl/OrderServiceImpl.java new file mode 100644 index 0000000..22710d7 --- /dev/null +++ b/src/main/java/cn/xf/basedemo/service/impl/OrderServiceImpl.java @@ -0,0 +1,153 @@ +package cn.xf.basedemo.service.impl; + +import cn.xf.basedemo.common.model.RetObj; +import cn.xf.basedemo.config.pay.AliPayConfigProperties; +import cn.xf.basedemo.model.req.PayOrderFrom; +import cn.xf.basedemo.model.res.PayOrderRes; +import cn.xf.basedemo.service.OrderService; +import com.alibaba.fastjson.JSONObject; +import com.alipay.api.AlipayClient; +import com.alipay.api.domain.AlipayTradeQueryModel; +import com.alipay.api.internal.util.AlipaySignature; +import com.alipay.api.request.AlipayTradePagePayRequest; +import com.alipay.api.request.AlipayTradeQueryRequest; +import com.alipay.api.response.AlipayTradePagePayResponse; +import com.alipay.api.response.AlipayTradeQueryResponse; +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import org.jetbrains.annotations.NotNull; +import org.springframework.stereotype.Service; + +import javax.servlet.http.HttpServletRequest; +import java.math.BigDecimal; +import java.util.HashMap; +import java.util.Map; + +/** + * OrderServiceImpl + * + * @author 海言 + * @date 2025/10/22 + * @time 14:08 + * @Description + */ +@Slf4j +@Service +public class OrderServiceImpl implements OrderService { + + @Resource + private AlipayClient alipayClient; + @Resource + private AliPayConfigProperties aliPayConfigProperties; + + @Override + public RetObj aliCreateOrder(PayOrderFrom from) { + + try { + //校验用户数据 + //校验商品库存 + //校验余额账户 + //... + //创建订单 + + //获取订单编码 + String orderNo = "O" + System.currentTimeMillis(); + // PC/H5方式支付 + AlipayTradePagePayRequest request = getAlipayTradePagePayRequest(from, orderNo); + AlipayTradePagePayResponse payResponse = alipayClient.pageExecute(request); + //创建支付单数据 + //... + String body = payResponse.getBody(); + PayOrderRes res = new PayOrderRes(); + res.setOrderNo(orderNo); + res.setFrom(body); + return RetObj.success(res); + } catch (Exception e) { + log.error("创建订单失败", e); + } + return null; + } + + @Override + public String aliCallback(HttpServletRequest request) { + try { + log.info("支付回调,消息接收 {}", request.getParameter("trade_status")); + if (request.getParameter("trade_status").equals("TRADE_SUCCESS")) { + Map params = new HashMap<>(); + Map requestParams = request.getParameterMap(); + for (String name : requestParams.keySet()) { + params.put(name, request.getParameter(name)); + } + String tradeNo = params.get("out_trade_no"); + String gmtPayment = params.get("gmt_payment"); + String alipayTradeNo = params.get("trade_no"); + String sign = params.get("sign"); + String content = AlipaySignature.getSignCheckContentV1(params); + boolean checkSignature = AlipaySignature.rsa256CheckContent(content, sign, aliPayConfigProperties.getAlipayPublicKey(), "UTF-8"); // 验证签名 + // 支付宝验签 + if (checkSignature) { + // 验签通过 + log.info("支付回调,交易名称: {}", params.get("subject")); + log.info("支付回调,交易状态: {}", params.get("trade_status")); + log.info("支付回调,支付宝交易凭证号: {}", params.get("trade_no")); + log.info("支付回调,商户订单号: {}", params.get("out_trade_no")); + log.info("支付回调,交易金额: {}", params.get("total_amount")); + log.info("支付回调,买家在支付宝唯一id: {}", params.get("buyer_id")); + log.info("支付回调,买家付款时间: {}", params.get("gmt_payment")); + log.info("支付回调,买家付款金额: {}", params.get("buyer_pay_amount")); + log.info("支付回调,支付回调,更新订单 {}", tradeNo); + + // 更新订单未已支付 +// orderService.changeOrderPaySuccess(tradeNo); + } + } + return "success"; + } catch (Exception e) { + log.error("支付回调,处理失败", e); + return "false"; + } + } + + /** + * 回查订单支付状态 + * + * @param orderNo + * @return + */ + @Override + public String queryAlipayOrderStatus(String orderNo) { + //实践方案:创建定时任务,查询订单数据中超过某个自定义时间未支付的订单进行回查 + try { + AlipayTradeQueryRequest request = new AlipayTradeQueryRequest(); + AlipayTradeQueryModel bizModel = new AlipayTradeQueryModel(); + bizModel.setOutTradeNo(orderNo); + request.setBizModel(bizModel); + AlipayTradeQueryResponse alipayTradeQueryResponse = alipayClient.execute(request); + String code = alipayTradeQueryResponse.getCode(); + // 判断状态码 + if ("10000".equals(code)) { + //更新订单状态 + //... + return "success"; + } + }catch (Exception e){ + log.error("回查支付订单失败", e); + } + return "not"; + } + + @NotNull + private AlipayTradePagePayRequest getAlipayTradePagePayRequest(PayOrderFrom from, String orderNo) { + AlipayTradePagePayRequest request = new AlipayTradePagePayRequest(); + request.setNotifyUrl("http://zfd8b38d.natappfree.cc/pay/callback/ali/notice"); + request.setReturnUrl("www.baidu.com"); + //构建请求参数 + JSONObject bizContent = new JSONObject(); + bizContent.put("out_trade_no", orderNo); + bizContent.put("total_amount", from.getPrice().multiply(new BigDecimal(from.getNum()))); + bizContent.put("subject", from.getProductName()); + bizContent.put("product_code", "FAST_INSTANT_TRADE_PAY"); + request.setBizContent(bizContent.toJSONString()); + return request; + } +} diff --git a/src/main/java/cn/xf/basedemo/service/impl/UserServiceImpl.java b/src/main/java/cn/xf/basedemo/service/impl/UserServiceImpl.java index 5191de7..21ff222 100644 --- a/src/main/java/cn/xf/basedemo/service/impl/UserServiceImpl.java +++ b/src/main/java/cn/xf/basedemo/service/impl/UserServiceImpl.java @@ -11,7 +11,7 @@ import cn.xf.basedemo.common.utils.StringUtil; import cn.xf.basedemo.config.GlobalConfig; import cn.xf.basedemo.mappers.UserMapper; import cn.xf.basedemo.model.domain.User; -import cn.xf.basedemo.model.res.LoginInfoRes; +import cn.xf.basedemo.model.req.LoginInfoReq; import cn.xf.basedemo.mq.RocketMqMsgProducer; import cn.xf.basedemo.service.UserService; import com.alibaba.fastjson.JSONObject; @@ -24,7 +24,6 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.stereotype.Service; import org.springframework.util.StringUtils; -import org.springframework.web.bind.annotation.RestController; import java.util.Objects; import java.util.concurrent.TimeUnit; @@ -56,7 +55,7 @@ public class UserServiceImpl implements UserService { private RocketMqMsgProducer rocketMqMsgProducer; @Override - public RetObj login(LoginInfoRes res) { + public RetObj login(LoginInfoReq res) { if (Objects.isNull(res) || StringUtils.isEmpty(res.getEncryptedData())) { return null;