diff --git a/pom.xml b/pom.xml
index 14913240..69e4b1ec 100644
--- a/pom.xml
+++ b/pom.xml
@@ -170,6 +170,13 @@
commons-pool2
+
+
+ com.alipay.sdk
+ alipay-sdk-java
+ 4.22.57.ALL
+
+
diff --git a/src/main/java/com/ai/da/common/config/AlipayClientConfig.java b/src/main/java/com/ai/da/common/config/AlipayClientConfig.java
new file mode 100644
index 00000000..5b1ec344
--- /dev/null
+++ b/src/main/java/com/ai/da/common/config/AlipayClientConfig.java
@@ -0,0 +1,43 @@
+package com.ai.da.common.config;
+
+import com.alipay.api.*;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.PropertySource;
+import org.springframework.core.env.Environment;
+
+import javax.annotation.Resource;
+
+@Configuration
+//加载配置文件
+@PropertySource("classpath:alipay-sandbox.properties")
+public class AlipayClientConfig {
+
+ @Resource
+ private Environment config;
+
+ @Bean
+ public AlipayClient alipayClient() throws AlipayApiException {
+
+ AlipayConfig alipayConfig = new AlipayConfig();
+
+ //设置网关地址
+ alipayConfig.setServerUrl(config.getProperty("alipay.gateway-url"));
+ //设置应用Id
+ alipayConfig.setAppId(config.getProperty("alipay.app-id"));
+ //设置应用私钥
+ alipayConfig.setPrivateKey(config.getProperty("alipay.merchant-private-key"));
+ //设置请求格式,固定值json
+ alipayConfig.setFormat(AlipayConstants.FORMAT_JSON);
+ //设置字符集
+ alipayConfig.setCharset(AlipayConstants.CHARSET_UTF8);
+ //设置支付宝公钥
+ alipayConfig.setAlipayPublicKey(config.getProperty("alipay.alipay-public-key"));
+ //设置签名类型
+ alipayConfig.setSignType(AlipayConstants.SIGN_TYPE_RSA2);
+ //构造client
+ AlipayClient alipayClient = new DefaultAlipayClient(alipayConfig);
+
+ return alipayClient;
+ }
+}
diff --git a/src/main/java/com/ai/da/common/enums/AliPayTradeStateEnum.java b/src/main/java/com/ai/da/common/enums/AliPayTradeStateEnum.java
new file mode 100644
index 00000000..573ec7dd
--- /dev/null
+++ b/src/main/java/com/ai/da/common/enums/AliPayTradeStateEnum.java
@@ -0,0 +1,39 @@
+package com.ai.da.common.enums;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+@AllArgsConstructor
+@Getter
+public enum AliPayTradeStateEnum {
+
+ /**
+ * 支付成功
+ */
+ SUCCESS("TRADE_SUCCESS"),
+
+ /**
+ * 未支付
+ */
+ NOTPAY("WAIT_BUYER_PAY"),
+
+ /**
+ * 已关闭
+ */
+ CLOSED("TRADE_CLOSED"),
+
+ /**
+ * 退款成功
+ */
+ REFUND_SUCCESS("REFUND_SUCCESS"),
+
+ /**
+ * 退款失败
+ */
+ REFUND_ERROR("REFUND_ERROR");
+
+ /**
+ * 类型
+ */
+ private final String type;
+}
diff --git a/src/main/java/com/ai/da/common/enums/OrderStatusEnum.java b/src/main/java/com/ai/da/common/enums/OrderStatusEnum.java
new file mode 100644
index 00000000..ede1e162
--- /dev/null
+++ b/src/main/java/com/ai/da/common/enums/OrderStatusEnum.java
@@ -0,0 +1,49 @@
+package com.ai.da.common.enums;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+@AllArgsConstructor
+@Getter
+public enum OrderStatusEnum {
+ /**
+ * 未支付
+ */
+ NOT_PAY("未支付"),
+
+
+ /**
+ * 支付成功
+ */
+ SUCCESS("支付成功"),
+
+ /**
+ * 已关闭
+ */
+ CLOSED("超时已关闭"),
+
+ /**
+ * 已取消
+ */
+ CANCEL("用户已取消"),
+
+ /**
+ * 退款中
+ */
+ REFUND_PROCESSING("退款中"),
+
+ /**
+ * 已退款
+ */
+ REFUND_SUCCESS("已退款"),
+
+ /**
+ * 退款异常
+ */
+ REFUND_ABNORMAL("退款异常");
+
+ /**
+ * 类型
+ */
+ private final String type;
+}
diff --git a/src/main/java/com/ai/da/common/enums/PayTypeEnum.java b/src/main/java/com/ai/da/common/enums/PayTypeEnum.java
new file mode 100644
index 00000000..cb2e89f8
--- /dev/null
+++ b/src/main/java/com/ai/da/common/enums/PayTypeEnum.java
@@ -0,0 +1,24 @@
+package com.ai.da.common.enums;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+@AllArgsConstructor
+@Getter
+public enum PayTypeEnum {
+ /**
+ * 微信
+ */
+ WXPAY("微信"),
+
+
+ /**
+ * 支付宝
+ */
+ ALIPAY("支付宝");
+
+ /**
+ * 类型
+ */
+ private final String type;
+}
diff --git a/src/main/java/com/ai/da/common/security/filter/AuthenticationFilter.java b/src/main/java/com/ai/da/common/security/filter/AuthenticationFilter.java
index 6efefd84..dc555916 100644
--- a/src/main/java/com/ai/da/common/security/filter/AuthenticationFilter.java
+++ b/src/main/java/com/ai/da/common/security/filter/AuthenticationFilter.java
@@ -94,7 +94,7 @@ public class AuthenticationFilter extends OncePerRequestFilter {
if (StrUtil.isBlank(jwtToken)) {
throw new RuntimeException("请传入token!");
}
- if(jwtToken.equals("Bearer-eyJhbGciOiJIUzUxMiJ9.eyJqdGkiOiIyIiwic3ViIjoie1wiaWRcIjoyLFwidXNlcm5hbWVcIjpcImxpcnNcIn0iLCJpYXQiOjE2NjU3NDEwODcsImlzcyI6IkRXSiIsImF1dGhvcml0aWVzIjoiW10iLCJleHAiOjE2NzQzODEwODd9.ShM9R_NNFD7oo1OvxrEgg7PFeWinOuAKkuInUCMQupp66s64Hhv8tN0Wwr83nIN4rHPqtn95wmd4msWcvaFYJA")){
+ if(jwtToken.equals("Bearer Bearer-eyJhbGciOiJIUzUxMiJ9.eyJqdGkiOiIyIiwic3ViIjoie1wiaWRcIjoyLFwidXNlcm5hbWVcIjpcImxpcnNcIn0iLCJpYXQiOjE2NjU3NDEwODcsImlzcyI6IkRXSiIsImF1dGhvcml0aWVzIjoiW10iLCJleHAiOjE2NzQzODEwODd9.ShM9R_NNFD7oo1OvxrEgg7PFeWinOuAKkuInUCMQupp66s64Hhv8tN0Wwr83nIN4rHPqtn95wmd4msWcvaFYJA")){
//写死 暂时放行
return;
}
diff --git a/src/main/java/com/ai/da/common/task/AliPayTask.java b/src/main/java/com/ai/da/common/task/AliPayTask.java
new file mode 100644
index 00000000..0d9c8390
--- /dev/null
+++ b/src/main/java/com/ai/da/common/task/AliPayTask.java
@@ -0,0 +1,42 @@
+package com.ai.da.common.task;
+
+import com.ai.da.mapper.entity.OrderInfo;
+import com.ai.da.common.enums.PayTypeEnum;
+import com.ai.da.service.AliPayService;
+import com.ai.da.service.OrderInfoService;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.scheduling.annotation.Scheduled;
+import org.springframework.stereotype.Component;
+
+import javax.annotation.Resource;
+import java.util.List;
+
+@Slf4j
+@Component
+public class AliPayTask {
+
+ @Resource
+ private OrderInfoService orderInfoService;
+
+ @Resource
+ private AliPayService aliPayService;
+
+ /**
+ * 从第0秒开始每隔30秒执行1次,查询创建超过5分钟,并且未支付的订单
+ */
+ @Scheduled(cron = "0/30 * * * * ?")
+ public void orderConfirm(){
+
+ log.info("orderConfirm 被执行......");
+
+ List orderInfoList = orderInfoService.getNoPayOrderByDuration(1, PayTypeEnum.ALIPAY.getType());
+
+ for (OrderInfo orderInfo : orderInfoList) {
+ String orderNo = orderInfo.getOrderNo();
+ log.warn("超时订单 ===> {}", orderNo);
+
+ //核实订单状态:调用支付宝查单接口
+ aliPayService.checkOrderStatus(orderNo);
+ }
+ }
+}
diff --git a/src/main/java/com/ai/da/common/utils/OrderNoUtils.java b/src/main/java/com/ai/da/common/utils/OrderNoUtils.java
new file mode 100644
index 00000000..2b0108de
--- /dev/null
+++ b/src/main/java/com/ai/da/common/utils/OrderNoUtils.java
@@ -0,0 +1,46 @@
+package com.ai.da.common.utils;
+
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.Random;
+
+/**
+ * 订单号工具类
+ *
+ * @author qy
+ * @since 1.0
+ */
+public class OrderNoUtils {
+
+ /**
+ * 获取订单编号
+ * @return
+ */
+ public static String getOrderNo() {
+ return "ORDER_" + getNo();
+ }
+
+ /**
+ * 获取退款单编号
+ * @return
+ */
+ public static String getRefundNo() {
+ return "REFUND_" + getNo();
+ }
+
+ /**
+ * 获取编号
+ * @return
+ */
+ public static String getNo() {
+ SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmss");
+ String newDate = sdf.format(new Date());
+ String result = "";
+ Random random = new Random();
+ for (int i = 0; i < 3; i++) {
+ result += random.nextInt(10);
+ }
+ return newDate + result;
+ }
+
+}
diff --git a/src/main/java/com/ai/da/controller/AliPayController.java b/src/main/java/com/ai/da/controller/AliPayController.java
new file mode 100644
index 00000000..e098b142
--- /dev/null
+++ b/src/main/java/com/ai/da/controller/AliPayController.java
@@ -0,0 +1,206 @@
+package com.ai.da.controller;
+
+import com.ai.da.common.response.Response;
+import com.ai.da.mapper.entity.OrderInfo;
+import com.ai.da.service.AliPayService;
+import com.ai.da.service.OrderInfoService;
+import com.alipay.api.AlipayApiException;
+import com.alipay.api.AlipayConstants;
+import com.alipay.api.internal.util.AlipaySignature;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.core.env.Environment;
+import org.springframework.web.bind.annotation.*;
+
+import javax.annotation.Resource;
+import java.math.BigDecimal;
+import java.util.Map;
+
+@CrossOrigin
+@RestController
+@RequestMapping("/api/ali-pay")
+@Api(tags = "网站支付宝支付")
+@Slf4j
+public class AliPayController {
+
+ @Resource
+ private AliPayService aliPayService;
+
+ @Resource
+ private Environment config;
+
+ @Resource
+ private OrderInfoService orderInfoService;
+
+ @ApiOperation("统一收单下单并支付页面接口的调用")
+ @PostMapping("/trade/page/pay/{productId}")
+ public Response tradePagePay(@PathVariable Long productId){
+
+ log.info("统一收单下单并支付页面接口的调用");
+ //支付宝开放平台接受 request 请求对象后
+ // 会为开发者生成一个html 形式的 form表单,包含自动提交的脚本
+ String formStr = aliPayService.tradeCreate(productId);
+ //我们将form表单字符串返回给前端程序,之后前端将会调用自动提交脚本,进行表单的提交
+ //此时,表单会自动提交到action属性所指向的支付宝开放平台中,从而为用户展示一个支付页面
+ return Response.success(formStr);
+ }
+
+ @ApiOperation("支付通知")
+ @PostMapping("/trade/notify")
+ public String tradeNotify(@RequestParam Map params){
+
+ log.info("支付通知正在执行");
+ log.info("通知参数 ===> {}", params);
+
+ String result = "failure";
+
+ try {
+ //异步通知验签
+ boolean signVerified = AlipaySignature.rsaCheckV1(
+ params,
+ config.getProperty("alipay.alipay-public-key"),
+ AlipayConstants.CHARSET_UTF8,
+ AlipayConstants.SIGN_TYPE_RSA2); //调用SDK验证签名
+
+ if(!signVerified){
+ //验签失败则记录异常日志,并在response中返回failure.
+ log.error("支付成功异步通知验签失败!");
+ return result;
+ }
+
+ // 验签成功后
+ log.info("支付成功异步通知验签成功!");
+
+ //按照支付结果异步通知中的描述,对支付结果中的业务内容进行二次校验,
+ //1 商户需要验证该通知数据中的 out_trade_no 是否为商户系统中创建的订单号
+ String outTradeNo = params.get("out_trade_no");
+ OrderInfo order = orderInfoService.getOrderByOrderNo(outTradeNo);
+ if(order == null){
+ log.error("订单不存在");
+ return result;
+ }
+
+ //2 判断 total_amount 是否确实为该订单的实际金额(即商户订单创建时的金额)
+ String totalAmount = params.get("total_amount");
+ int totalAmountInt = new BigDecimal(totalAmount).multiply(new BigDecimal("100")).intValue();
+ int totalFeeInt = order.getTotalFee().intValue();
+ if(totalAmountInt != totalFeeInt){
+ log.error("金额校验失败");
+ return result;
+ }
+
+ //3 校验通知中的 seller_id(或者 seller_email) 是否为 out_trade_no 这笔单据的对应的操作方
+ String sellerId = params.get("seller_id");
+ String sellerIdProperty = config.getProperty("alipay.seller-id");
+ if(!sellerId.equals(sellerIdProperty)){
+ log.error("商家pid校验失败");
+ return result;
+ }
+
+ //4 验证 app_id 是否为该商户本身
+ String appId = params.get("app_id");
+ String appIdProperty = config.getProperty("alipay.app-id");
+ if(!appId.equals(appIdProperty)){
+ log.error("appid校验失败");
+ return result;
+ }
+
+ //在支付宝的业务通知中,只有交易通知状态为 TRADE_SUCCESS时,
+ // 支付宝才会认定为买家付款成功。
+ String tradeStatus = params.get("trade_status");
+ if(!"TRADE_SUCCESS".equals(tradeStatus)){
+ log.error("支付未成功");
+ return result;
+ }
+
+ //处理业务 修改订单状态 记录支付日志
+ aliPayService.processOrder(params);
+
+ //校验成功后在response中返回success并继续商户自身业务处理,校验失败返回failure
+ result = "success";
+ } catch (AlipayApiException e) {
+ e.printStackTrace();
+ }
+ return result;
+ }
+
+ /**
+ * 用户取消订单
+ * @param orderNo
+ * @return
+ */
+ @ApiOperation("用户取消订单")
+ @PostMapping("/trade/close/{orderNo}")
+ public Response cancel(@PathVariable String orderNo){
+
+ log.info("取消订单");
+ aliPayService.cancelOrder(orderNo);
+ return Response.success("订单已取消");
+ }
+
+ /**
+ * 查询订单
+ * @param orderNo
+ * @return
+ */
+ @ApiOperation("查询订单:测试订单状态用")
+ @GetMapping("/trade/query/{orderNo}")
+ public Response queryOrder(@PathVariable String orderNo) {
+
+ log.info("查询订单");
+
+ String result = aliPayService.queryOrder(orderNo);
+ return Response.success(result);
+
+ }
+
+ /**
+ * 申请退款
+ * @param orderNo
+ * @param reason
+ * @return
+ */
+ @ApiOperation("申请退款")
+ @PostMapping("/trade/refund/{orderNo}/{reason}")
+ public Response refunds(@PathVariable String orderNo, @PathVariable String reason){
+
+ log.info("申请退款");
+ aliPayService.refund(orderNo, reason);
+ return Response.success();
+ }
+
+ /**
+ * 查询退款
+ * @param orderNo
+ * @return
+ * @throws Exception
+ */
+ @ApiOperation("查询退款:测试用")
+ @GetMapping("/trade/fastpay/refund/{orderNo}")
+ public Response queryRefund(@PathVariable String orderNo) throws Exception {
+
+ log.info("查询退款");
+
+ String result = aliPayService.queryRefund(orderNo);
+ return Response.success(result);
+ }
+
+ /**
+ * 根据账单类型和日期获取账单url地址
+ *
+ * @param billDate
+ * @param type
+ * @return
+ */
+ @ApiOperation("获取账单url")
+ @GetMapping("/bill/downloadurl/query/{billDate}/{type}")
+ public Response queryTradeBill(
+ @PathVariable String billDate,
+ @PathVariable String type) {
+ log.info("获取账单url");
+ String downloadUrl = aliPayService.queryBill(billDate, type);
+ return Response.success(downloadUrl);
+ }
+
+}
diff --git a/src/main/java/com/ai/da/controller/OrderInfoController.java b/src/main/java/com/ai/da/controller/OrderInfoController.java
new file mode 100644
index 00000000..b71d0e04
--- /dev/null
+++ b/src/main/java/com/ai/da/controller/OrderInfoController.java
@@ -0,0 +1,50 @@
+package com.ai.da.controller;
+
+import com.ai.da.common.enums.OrderStatusEnum;
+import com.ai.da.common.response.Response;
+import com.ai.da.mapper.entity.OrderInfo;
+import com.ai.da.service.OrderInfoService;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import org.springframework.web.bind.annotation.*;
+
+import javax.annotation.Resource;
+import java.util.List;
+
+@CrossOrigin //开放前端的跨域访问
+@Api(tags = "商品订单管理")
+@RestController
+@RequestMapping("/api/order-info")
+public class OrderInfoController {
+
+ @Resource
+ private OrderInfoService orderInfoService;
+
+ @ApiOperation("订单列表")
+ @GetMapping("/list")
+ public Response> list(){
+
+ List list = orderInfoService.listOrderByCreateTimeDesc();
+ return Response.success(list);
+ }
+
+ /**
+ * 查询本地订单状态
+ * @param orderNo
+ * @return
+ */
+ @ApiOperation("查询本地订单状态")
+ @GetMapping("/query-order-status/{orderNo}")
+ public Response queryOrderStatus(@PathVariable String orderNo){
+
+ String orderStatus = orderInfoService.getOrderStatus(orderNo);
+ if(OrderStatusEnum.SUCCESS.getType().equals(orderStatus)){
+ return Response.success("支付成功"); //支付成功
+ }
+
+ return Response.success(101,"支付中......");
+ }
+
+
+
+}
diff --git a/src/main/java/com/ai/da/controller/ProductController.java b/src/main/java/com/ai/da/controller/ProductController.java
new file mode 100644
index 00000000..b22b1537
--- /dev/null
+++ b/src/main/java/com/ai/da/controller/ProductController.java
@@ -0,0 +1,42 @@
+package com.ai.da.controller;
+
+
+import com.ai.da.common.response.Response;
+import com.ai.da.mapper.entity.Product;
+import com.ai.da.service.ProductService;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import org.springframework.web.bind.annotation.CrossOrigin;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import javax.annotation.Resource;
+import java.util.Date;
+import java.util.List;
+
+@CrossOrigin //开放前端的跨域访问
+@Api(tags = "商品管理")
+@RestController
+@RequestMapping("/api/product")
+public class ProductController {
+
+ @Resource
+ private ProductService productService;
+
+ @ApiOperation("测试接口")
+ @GetMapping("/test")
+ public Response test(){
+
+ return Response.success("now" + new Date());
+ }
+
+ @ApiOperation("商品列表")
+ @GetMapping("/list")
+ public Response> list(){
+
+ List list = productService.list();
+ return Response.success(list);
+ }
+
+}
diff --git a/src/main/java/com/ai/da/mapper/OrderInfoMapper.java b/src/main/java/com/ai/da/mapper/OrderInfoMapper.java
new file mode 100644
index 00000000..4b8b6d68
--- /dev/null
+++ b/src/main/java/com/ai/da/mapper/OrderInfoMapper.java
@@ -0,0 +1,8 @@
+package com.ai.da.mapper;
+
+import com.ai.da.mapper.entity.OrderInfo;
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+
+public interface OrderInfoMapper extends BaseMapper {
+
+}
diff --git a/src/main/java/com/ai/da/mapper/PaymentInfoMapper.java b/src/main/java/com/ai/da/mapper/PaymentInfoMapper.java
new file mode 100644
index 00000000..1406b6bc
--- /dev/null
+++ b/src/main/java/com/ai/da/mapper/PaymentInfoMapper.java
@@ -0,0 +1,7 @@
+package com.ai.da.mapper;
+
+import com.ai.da.mapper.entity.PaymentInfo;
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+
+public interface PaymentInfoMapper extends BaseMapper {
+}
diff --git a/src/main/java/com/ai/da/mapper/ProductMapper.java b/src/main/java/com/ai/da/mapper/ProductMapper.java
new file mode 100644
index 00000000..1ffdf2eb
--- /dev/null
+++ b/src/main/java/com/ai/da/mapper/ProductMapper.java
@@ -0,0 +1,8 @@
+package com.ai.da.mapper;
+
+import com.ai.da.mapper.entity.Product;
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+
+public interface ProductMapper extends BaseMapper {
+
+}
diff --git a/src/main/java/com/ai/da/mapper/RefundInfoMapper.java b/src/main/java/com/ai/da/mapper/RefundInfoMapper.java
new file mode 100644
index 00000000..8f9c746e
--- /dev/null
+++ b/src/main/java/com/ai/da/mapper/RefundInfoMapper.java
@@ -0,0 +1,9 @@
+package com.ai.da.mapper;
+
+
+import com.ai.da.mapper.entity.RefundInfo;
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+
+public interface RefundInfoMapper extends BaseMapper {
+
+}
diff --git a/src/main/java/com/ai/da/mapper/entity/BaseEntity.java b/src/main/java/com/ai/da/mapper/entity/BaseEntity.java
new file mode 100644
index 00000000..05d83fa0
--- /dev/null
+++ b/src/main/java/com/ai/da/mapper/entity/BaseEntity.java
@@ -0,0 +1,19 @@
+package com.ai.da.mapper.entity;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import lombok.Data;
+
+import java.util.Date;
+
+@Data
+public class BaseEntity {
+
+ //定义主键策略:跟随数据库的主键自增
+ @TableId(value = "id", type = IdType.AUTO)
+ private String id; //主键
+
+ private Date createTime;//创建时间
+
+ private Date updateTime;//更新时间
+}
diff --git a/src/main/java/com/ai/da/mapper/entity/OrderInfo.java b/src/main/java/com/ai/da/mapper/entity/OrderInfo.java
new file mode 100644
index 00000000..12255f12
--- /dev/null
+++ b/src/main/java/com/ai/da/mapper/entity/OrderInfo.java
@@ -0,0 +1,25 @@
+package com.ai.da.mapper.entity;
+
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+
+@Data
+@TableName("t_order_info")
+public class OrderInfo extends BaseEntity{
+
+ private String title;//订单标题
+
+ private String orderNo;//商户订单编号
+
+ private Long userId;//用户id
+
+ private Long productId;//支付产品id
+
+ private Integer totalFee;//订单金额(分)
+
+ private String codeUrl;//订单二维码连接
+
+ private String orderStatus;//订单状态
+
+ private String paymentType;//支付方式
+}
diff --git a/src/main/java/com/ai/da/mapper/entity/PaymentInfo.java b/src/main/java/com/ai/da/mapper/entity/PaymentInfo.java
new file mode 100644
index 00000000..ceacf94a
--- /dev/null
+++ b/src/main/java/com/ai/da/mapper/entity/PaymentInfo.java
@@ -0,0 +1,23 @@
+package com.ai.da.mapper.entity;
+
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+
+@Data
+@TableName("t_payment_info")
+public class PaymentInfo extends BaseEntity{
+
+ private String orderNo;//商品订单编号
+
+ private String transactionId;//支付系统交易编号
+
+ private String paymentType;//支付类型
+
+ private String tradeType;//交易类型
+
+ private String tradeState;//交易状态
+
+ private Integer payerTotal;//支付金额(分)
+
+ private String content;//通知参数
+}
diff --git a/src/main/java/com/ai/da/mapper/entity/Product.java b/src/main/java/com/ai/da/mapper/entity/Product.java
new file mode 100644
index 00000000..9aa55531
--- /dev/null
+++ b/src/main/java/com/ai/da/mapper/entity/Product.java
@@ -0,0 +1,13 @@
+package com.ai.da.mapper.entity;
+
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+
+@Data
+@TableName("t_product")
+public class Product extends BaseEntity{
+
+ private String title; //商品名称
+
+ private Integer price; //价格(分)
+}
diff --git a/src/main/java/com/ai/da/mapper/entity/RefundInfo.java b/src/main/java/com/ai/da/mapper/entity/RefundInfo.java
new file mode 100644
index 00000000..594be318
--- /dev/null
+++ b/src/main/java/com/ai/da/mapper/entity/RefundInfo.java
@@ -0,0 +1,27 @@
+package com.ai.da.mapper.entity;
+
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+
+@Data
+@TableName("t_refund_info")
+public class RefundInfo extends BaseEntity{
+
+ private String orderNo;//商品订单编号
+
+ private String refundNo;//退款单编号
+
+ private String refundId;//支付系统退款单号
+
+ private Integer totalFee;//原订单金额(分)
+
+ private Integer refund;//退款金额(分)
+
+ private String reason;//退款原因
+
+ private String refundStatus;//退款单状态
+
+ private String contentReturn;//申请退款返回参数
+
+ private String contentNotify;//退款结果通知参数
+}
diff --git a/src/main/java/com/ai/da/service/AliPayService.java b/src/main/java/com/ai/da/service/AliPayService.java
new file mode 100644
index 00000000..f2d4e39b
--- /dev/null
+++ b/src/main/java/com/ai/da/service/AliPayService.java
@@ -0,0 +1,22 @@
+package com.ai.da.service;
+
+import java.util.Map;
+
+public interface AliPayService {
+ String tradeCreate(Long productId);
+
+ void processOrder(Map params);
+
+ void cancelOrder(String orderNo);
+
+ String queryOrder(String orderNo);
+
+ void checkOrderStatus(String orderNo);
+
+ void refund(String orderNo, String reason);
+
+ String queryRefund(String orderNo);
+
+ String queryBill(String billDate, String type);
+
+}
diff --git a/src/main/java/com/ai/da/service/OrderInfoService.java b/src/main/java/com/ai/da/service/OrderInfoService.java
new file mode 100644
index 00000000..1507d931
--- /dev/null
+++ b/src/main/java/com/ai/da/service/OrderInfoService.java
@@ -0,0 +1,25 @@
+package com.ai.da.service;
+
+
+import com.ai.da.common.enums.OrderStatusEnum;
+import com.ai.da.mapper.entity.OrderInfo;
+import com.baomidou.mybatisplus.extension.service.IService;
+
+import java.util.List;
+
+public interface OrderInfoService extends IService {
+
+ OrderInfo createOrderByProductId(Long productId, String paymentType);
+
+ void saveCodeUrl(String orderNo, String codeUrl);
+
+ List listOrderByCreateTimeDesc();
+
+ void updateStatusByOrderNo(String orderNo, OrderStatusEnum orderStatus);
+
+ String getOrderStatus(String orderNo);
+
+ List getNoPayOrderByDuration(int minutes, String paymentType);
+
+ OrderInfo getOrderByOrderNo(String orderNo);
+}
diff --git a/src/main/java/com/ai/da/service/PaymentInfoService.java b/src/main/java/com/ai/da/service/PaymentInfoService.java
new file mode 100644
index 00000000..aa1470e4
--- /dev/null
+++ b/src/main/java/com/ai/da/service/PaymentInfoService.java
@@ -0,0 +1,10 @@
+package com.ai.da.service;
+
+import java.util.Map;
+
+public interface PaymentInfoService {
+
+ void createPaymentInfo(String plainText);
+
+ void createPaymentInfoForAliPay(Map params);
+}
diff --git a/src/main/java/com/ai/da/service/ProductService.java b/src/main/java/com/ai/da/service/ProductService.java
new file mode 100644
index 00000000..9ac2597d
--- /dev/null
+++ b/src/main/java/com/ai/da/service/ProductService.java
@@ -0,0 +1,8 @@
+package com.ai.da.service;
+
+import com.ai.da.mapper.entity.Product;
+import com.baomidou.mybatisplus.extension.service.IService;
+
+public interface ProductService extends IService {
+
+}
diff --git a/src/main/java/com/ai/da/service/RefundInfoService.java b/src/main/java/com/ai/da/service/RefundInfoService.java
new file mode 100644
index 00000000..014004cd
--- /dev/null
+++ b/src/main/java/com/ai/da/service/RefundInfoService.java
@@ -0,0 +1,20 @@
+package com.ai.da.service;
+
+
+import com.ai.da.mapper.entity.RefundInfo;
+import com.baomidou.mybatisplus.extension.service.IService;
+
+import java.util.List;
+
+public interface RefundInfoService extends IService {
+
+ RefundInfo createRefundByOrderNo(String orderNo, String reason);
+
+ void updateRefund(String content);
+
+ List getNoRefundOrderByDuration(int minutes);
+
+ RefundInfo createRefundByOrderNoForAliPay(String orderNo, String reason);
+
+ void updateRefundForAliPay(String refundNo, String content, String refundStatus);
+}
diff --git a/src/main/java/com/ai/da/service/impl/AliPayServiceImpl.java b/src/main/java/com/ai/da/service/impl/AliPayServiceImpl.java
new file mode 100644
index 00000000..a255dbb0
--- /dev/null
+++ b/src/main/java/com/ai/da/service/impl/AliPayServiceImpl.java
@@ -0,0 +1,394 @@
+package com.ai.da.service.impl;
+
+import com.ai.da.common.enums.AliPayTradeStateEnum;
+import com.ai.da.common.enums.OrderStatusEnum;
+import com.ai.da.common.enums.PayTypeEnum;
+import com.ai.da.mapper.entity.OrderInfo;
+import com.ai.da.mapper.entity.RefundInfo;
+import com.ai.da.service.AliPayService;
+import com.ai.da.service.OrderInfoService;
+import com.ai.da.service.PaymentInfoService;
+import com.ai.da.service.RefundInfoService;
+import com.alibaba.fastjson.JSONObject;
+import com.alipay.api.AlipayApiException;
+import com.alipay.api.AlipayClient;
+import com.alipay.api.request.*;
+import com.alipay.api.response.*;
+import com.google.gson.Gson;
+import com.google.gson.internal.LinkedTreeMap;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.core.env.Environment;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import javax.annotation.Resource;
+import java.math.BigDecimal;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.locks.ReentrantLock;
+
+@Service
+@Slf4j
+public class AliPayServiceImpl implements AliPayService {
+
+ @Resource
+ private OrderInfoService orderInfoService;
+
+ @Resource
+ private AlipayClient alipayClient;
+
+ @Resource
+ private Environment config;
+
+ @Resource
+ private PaymentInfoService paymentInfoService;
+
+ @Resource
+ private RefundInfoService refundsInfoService;
+
+ private final ReentrantLock lock = new ReentrantLock();
+
+ @Transactional(rollbackFor = Exception.class)
+ @Override
+ public String tradeCreate(Long productId) {
+
+ try {
+ //生成订单
+ log.info("生成订单");
+ OrderInfo orderInfo = orderInfoService.createOrderByProductId(productId, PayTypeEnum.ALIPAY.getType());
+
+ //调用支付宝接口
+ AlipayTradePagePayRequest request = new AlipayTradePagePayRequest();
+ //配置需要的公共请求参数
+ //支付完成后,支付宝向谷粒学院发起异步通知的地址
+ request.setNotifyUrl(config.getProperty("alipay.notify-url"));
+ //支付完成后,我们想让页面跳转回谷粒学院的页面,配置returnUrl
+ request.setReturnUrl(config.getProperty("alipay.return-url"));
+
+ //组装当前业务方法的请求参数
+ JSONObject bizContent = new JSONObject();
+ bizContent.put("out_trade_no", orderInfo.getOrderNo());
+ BigDecimal total = new BigDecimal(orderInfo.getTotalFee().toString()).divide(new BigDecimal("100"));
+ bizContent.put("total_amount", total);
+ bizContent.put("subject", orderInfo.getTitle());
+ bizContent.put("product_code", "FAST_INSTANT_TRADE_PAY");
+
+ request.setBizContent(bizContent.toString());
+
+ //执行请求,调用支付宝接口
+ AlipayTradePagePayResponse response = alipayClient.pageExecute(request);
+
+ if(response.isSuccess()){
+ log.info("调用成功,返回结果 ===> " + response.getBody());
+ return response.getBody();
+ } else {
+ log.info("调用失败,返回码 ===> " + response.getCode() + ", 返回描述 ===> " + response.getMsg());
+ throw new RuntimeException("创建支付交易失败");
+ }
+ } catch (AlipayApiException e) {
+ e.printStackTrace();
+ throw new RuntimeException("创建支付交易失败");
+ }
+ }
+
+ /**
+ * 处理订单
+ * @param params
+ */
+ @Transactional(rollbackFor = Exception.class)
+ @Override
+ public void processOrder(Map params) {
+
+ log.info("处理订单");
+
+ //获取订单号
+ String orderNo = params.get("out_trade_no");
+
+ /*在对业务数据进行状态检查和处理之前,
+ 要采用数据锁进行并发控制,
+ 以避免函数重入造成的数据混乱*/
+ //尝试获取锁:
+ // 成功获取则立即返回true,获取失败则立即返回false。不必一直等待锁的释放
+ if(lock.tryLock()) {
+ try {
+
+ //处理重复通知
+ //接口调用的幂等性:无论接口被调用多少次,以下业务执行一次
+ String orderStatus = orderInfoService.getOrderStatus(orderNo);
+ if (!OrderStatusEnum.NOT_PAY.getType().equals(orderStatus)) {
+ return;
+ }
+
+ //更新订单状态
+ orderInfoService.updateStatusByOrderNo(orderNo, OrderStatusEnum.SUCCESS);
+
+ //记录支付日志
+ paymentInfoService.createPaymentInfoForAliPay(params);
+
+ } finally {
+ //要主动释放锁
+ lock.unlock();
+ }
+ }
+
+ }
+
+ /**
+ * 用户取消订单
+ * @param orderNo
+ */
+ @Override
+ public void cancelOrder(String orderNo) {
+
+ //调用支付宝提供的统一收单交易关闭接口
+ this.closeOrder(orderNo);
+
+ //更新用户订单状态
+ orderInfoService.updateStatusByOrderNo(orderNo, OrderStatusEnum.CANCEL);
+ }
+
+ /**
+ * 查询订单
+ * @param orderNo
+ * @return 返回订单查询结果,如果返回null则表示支付宝端尚未创建订单
+ */
+ @Override
+ public String queryOrder(String orderNo) {
+
+ try {
+ log.info("查单接口调用 ===> {}", orderNo);
+
+ AlipayTradeQueryRequest request = new AlipayTradeQueryRequest();
+ JSONObject bizContent = new JSONObject();
+ bizContent.put("out_trade_no", orderNo);
+ request.setBizContent(bizContent.toString());
+
+ AlipayTradeQueryResponse response = alipayClient.execute(request);
+ if(response.isSuccess()){
+ log.info("调用成功,返回结果 ===> " + response.getBody());
+ return response.getBody();
+ } else {
+ log.info("调用失败,返回码 ===> " + response.getCode() + ", 返回描述 ===> " + response.getMsg());
+ //throw new RuntimeException("查单接口的调用失败");
+ return null;//订单不存在
+ }
+
+ } catch (AlipayApiException e) {
+ e.printStackTrace();
+ throw new RuntimeException("查单接口的调用失败");
+ }
+ }
+
+ /**
+ * 根据订单号调用支付宝查单接口,核实订单状态
+ * 如果订单未创建,则更新商户端订单状态
+ * 如果订单未支付,则调用关单接口关闭订单,并更新商户端订单状态
+ * 如果订单已支付,则更新商户端订单状态,并记录支付日志
+ * @param orderNo
+ */
+ @Override
+ public void checkOrderStatus(String orderNo) {
+
+ log.warn("根据订单号核实订单状态 ===> {}", orderNo);
+
+ String result = this.queryOrder(orderNo);
+
+ //订单未创建
+ if(result == null){
+ log.warn("核实订单未创建 ===> {}", orderNo);
+ //更新本地订单状态
+ orderInfoService.updateStatusByOrderNo(orderNo, OrderStatusEnum.CLOSED);
+ }
+
+ //解析查单响应结果
+ Gson gson = new Gson();
+ HashMap resultMap = gson.fromJson(result, HashMap.class);
+ LinkedTreeMap alipayTradeQueryResponse = resultMap.get("alipay_trade_query_response");
+
+ String tradeStatus = (String)alipayTradeQueryResponse.get("trade_status");
+ if(AliPayTradeStateEnum.NOTPAY.getType().equals(tradeStatus)){
+ log.warn("核实订单未支付 ===> {}", orderNo);
+
+ //如果订单未支付,则调用关单接口关闭订单
+ this.closeOrder(orderNo);
+
+ // 并更新商户端订单状态
+ orderInfoService.updateStatusByOrderNo(orderNo, OrderStatusEnum.CLOSED);
+ }
+
+ if(AliPayTradeStateEnum.SUCCESS.getType().equals(tradeStatus)){
+ log.warn("核实订单已支付 ===> {}", orderNo);
+
+ //如果订单已支付,则更新商户端订单状态
+ orderInfoService.updateStatusByOrderNo(orderNo, OrderStatusEnum.SUCCESS);
+
+ //并记录支付日志
+ paymentInfoService.createPaymentInfoForAliPay(alipayTradeQueryResponse);
+ }
+
+ }
+
+ /**
+ * 关单接口的调用
+ * @param orderNo 订单号
+ */
+ private void closeOrder(String orderNo) {
+
+ try {
+ log.info("关单接口的调用,订单号 ===> {}", orderNo);
+
+ AlipayTradeCloseRequest request = new AlipayTradeCloseRequest();
+ JSONObject bizContent = new JSONObject();
+ bizContent.put("out_trade_no", orderNo);
+ request.setBizContent(bizContent.toString());
+ AlipayTradeCloseResponse response = alipayClient.execute(request);
+
+ if(response.isSuccess()){
+ log.info("调用成功,返回结果 ===> " + response.getBody());
+ } else {
+ log.info("调用失败,返回码 ===> " + response.getCode() + ", 返回描述 ===> " + response.getMsg());
+ //throw new RuntimeException("关单接口的调用失败");
+ }
+
+ } catch (AlipayApiException e) {
+ e.printStackTrace();
+ throw new RuntimeException("关单接口的调用失败");
+ }
+ }
+
+ /**
+ * 退款
+ * @param orderNo
+ * @param reason
+ */
+ @Transactional(rollbackFor = Exception.class)
+ @Override
+ public void refund(String orderNo, String reason) {
+
+ try {
+ log.info("调用退款API");
+
+ //创建退款单
+ RefundInfo refundInfo = refundsInfoService.createRefundByOrderNoForAliPay(orderNo, reason);
+
+ //调用统一收单交易退款接口
+ AlipayTradeRefundRequest request = new AlipayTradeRefundRequest ();
+
+ //组装当前业务方法的请求参数
+ JSONObject bizContent = new JSONObject();
+ bizContent.put("out_trade_no", orderNo);//订单编号
+ BigDecimal refund = new BigDecimal(refundInfo.getRefund().toString()).divide(new BigDecimal("100"));
+ //BigDecimal refund = new BigDecimal("2").divide(new BigDecimal("100"));
+ bizContent.put("refund_amount", refund);//退款金额:不能大于支付金额
+ bizContent.put("refund_reason", reason);//退款原因(可选)
+
+ request.setBizContent(bizContent.toString());
+
+ //执行请求,调用支付宝接口
+ AlipayTradeRefundResponse response = alipayClient.execute(request);
+
+ if(response.isSuccess()){
+ log.info("调用成功,返回结果 ===> " + response.getBody());
+
+ //更新订单状态
+ orderInfoService.updateStatusByOrderNo(orderNo, OrderStatusEnum.REFUND_SUCCESS);
+
+ //更新退款单
+ refundsInfoService.updateRefundForAliPay(
+ refundInfo.getRefundNo(),
+ response.getBody(),
+ AliPayTradeStateEnum.REFUND_SUCCESS.getType()); //退款成功
+
+ } else {
+ log.info("调用失败,返回码 ===> " + response.getCode() + ", 返回描述 ===> " + response.getMsg());
+
+ //更新订单状态
+ orderInfoService.updateStatusByOrderNo(orderNo, OrderStatusEnum.REFUND_ABNORMAL);
+
+ //更新退款单
+ refundsInfoService.updateRefundForAliPay(
+ refundInfo.getRefundNo(),
+ response.getBody(),
+ AliPayTradeStateEnum.REFUND_ERROR.getType()); //退款失败
+ }
+
+
+ } catch (AlipayApiException e) {
+ e.printStackTrace();
+ throw new RuntimeException("创建退款申请失败");
+ }
+ }
+
+ /**
+ * 查询退款
+ * @param orderNo
+ * @return
+ */
+ @Override
+ public String queryRefund(String orderNo) {
+
+ try {
+ log.info("查询退款接口调用 ===> {}", orderNo);
+
+ AlipayTradeFastpayRefundQueryRequest request = new AlipayTradeFastpayRefundQueryRequest();
+ JSONObject bizContent = new JSONObject();
+ bizContent.put("out_trade_no", orderNo);
+ bizContent.put("out_request_no", orderNo);
+ request.setBizContent(bizContent.toString());
+
+ AlipayTradeFastpayRefundQueryResponse response = alipayClient.execute(request);
+ if(response.isSuccess()){
+ log.info("调用成功,返回结果 ===> " + response.getBody());
+ return response.getBody();
+ } else {
+ log.info("调用失败,返回码 ===> " + response.getCode() + ", 返回描述 ===> " + response.getMsg());
+ //throw new RuntimeException("查单接口的调用失败");
+ return null;//订单不存在
+ }
+
+ } catch (AlipayApiException e) {
+ e.printStackTrace();
+ throw new RuntimeException("查单接口的调用失败");
+ }
+ }
+
+ /**
+ * 申请账单
+ * @param billDate
+ * @param type
+ * @return
+ */
+ @Override
+ public String queryBill(String billDate, String type) {
+
+ try {
+
+ AlipayDataDataserviceBillDownloadurlQueryRequest request = new AlipayDataDataserviceBillDownloadurlQueryRequest();
+ JSONObject bizContent = new JSONObject();
+ bizContent.put("bill_type", type);
+ bizContent.put("bill_date", billDate);
+ request.setBizContent(bizContent.toString());
+ AlipayDataDataserviceBillDownloadurlQueryResponse response = alipayClient.execute(request);
+
+ if(response.isSuccess()){
+ log.info("调用成功,返回结果 ===> " + response.getBody());
+
+ //获取账单下载地址
+ Gson gson = new Gson();
+ HashMap resultMap = gson.fromJson(response.getBody(), HashMap.class);
+ LinkedTreeMap billDownloadurlResponse = resultMap.get("alipay_data_dataservice_bill_downloadurl_query_response");
+ String billDownloadUrl = (String)billDownloadurlResponse.get("bill_download_url");
+
+ return billDownloadUrl;
+ } else {
+ log.info("调用失败,返回码 ===> " + response.getCode() + ", 返回描述 ===> " + response.getMsg());
+ throw new RuntimeException("申请账单失败");
+ }
+
+ } catch (AlipayApiException e) {
+ e.printStackTrace();
+ throw new RuntimeException("申请账单失败");
+ }
+ }
+
+}
diff --git a/src/main/java/com/ai/da/service/impl/OrderInfoServiceImpl.java b/src/main/java/com/ai/da/service/impl/OrderInfoServiceImpl.java
new file mode 100644
index 00000000..aa1f5255
--- /dev/null
+++ b/src/main/java/com/ai/da/service/impl/OrderInfoServiceImpl.java
@@ -0,0 +1,172 @@
+package com.ai.da.service.impl;
+
+
+import com.ai.da.common.enums.OrderStatusEnum;
+import com.ai.da.common.utils.OrderNoUtils;
+import com.ai.da.mapper.OrderInfoMapper;
+import com.ai.da.mapper.ProductMapper;
+import com.ai.da.mapper.entity.OrderInfo;
+import com.ai.da.mapper.entity.Product;
+import com.ai.da.service.OrderInfoService;
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+
+import javax.annotation.Resource;
+import java.time.Duration;
+import java.time.Instant;
+import java.util.List;
+
+@Service
+@Slf4j
+public class OrderInfoServiceImpl extends ServiceImpl implements OrderInfoService {
+
+ @Resource
+ private ProductMapper productMapper;
+
+ /*@Resource
+ private OrderInfoMapper orderInfoMapper;*/
+
+ @Override
+ public OrderInfo createOrderByProductId(Long productId, String paymentType) {
+
+ //查找已存在但未支付的订单
+ OrderInfo orderInfo = this.getNoPayOrderByProductId(productId, paymentType);
+ if( orderInfo != null){
+ return orderInfo;
+ }
+
+ //获取商品信息
+ Product product = productMapper.selectById(productId);
+
+ //生成订单
+ orderInfo = new OrderInfo();
+ orderInfo.setTitle(product.getTitle());
+ orderInfo.setOrderNo(OrderNoUtils.getOrderNo()); //订单号
+ orderInfo.setProductId(productId);
+ orderInfo.setTotalFee(product.getPrice()); //分
+ orderInfo.setOrderStatus(OrderStatusEnum.NOT_PAY.getType()); //未支付
+ orderInfo.setPaymentType(paymentType);
+ baseMapper.insert(orderInfo);
+
+ return orderInfo;
+ }
+
+ /**
+ * 存储订单二维码
+ * @param orderNo
+ * @param codeUrl
+ */
+ @Override
+ public void saveCodeUrl(String orderNo, String codeUrl) {
+
+ QueryWrapper queryWrapper = new QueryWrapper<>();
+ queryWrapper.eq("order_no", orderNo);
+
+ OrderInfo orderInfo = new OrderInfo();
+ orderInfo.setCodeUrl(codeUrl);
+
+ baseMapper.update(orderInfo, queryWrapper);
+ }
+
+ /**
+ * 查询订单列表,并倒序查询
+ * @return
+ */
+ @Override
+ public List listOrderByCreateTimeDesc() {
+
+ QueryWrapper queryWrapper = new QueryWrapper().orderByDesc("create_time");
+ return baseMapper.selectList(queryWrapper);
+ }
+
+ /**
+ * 根据订单号更新订单状态
+ * @param orderNo
+ * @param orderStatus
+ */
+ @Override
+ public void updateStatusByOrderNo(String orderNo, OrderStatusEnum orderStatus) {
+
+ log.info("更新订单状态 ===> {}", orderStatus.getType());
+
+ QueryWrapper queryWrapper = new QueryWrapper<>();
+ queryWrapper.eq("order_no", orderNo);
+
+ OrderInfo orderInfo = new OrderInfo();
+ orderInfo.setOrderStatus(orderStatus.getType());
+
+ baseMapper.update(orderInfo, queryWrapper);
+ }
+
+ /**
+ * 根据订单号获取订单状态
+ * @param orderNo
+ * @return
+ */
+ @Override
+ public String getOrderStatus(String orderNo) {
+
+ QueryWrapper queryWrapper = new QueryWrapper<>();
+ queryWrapper.eq("order_no", orderNo);
+ OrderInfo orderInfo = baseMapper.selectOne(queryWrapper);
+ if(orderInfo == null){
+ return null;
+ }
+ return orderInfo.getOrderStatus();
+ }
+
+ /**
+ * 查询创建超过minutes分钟并且未支付的订单
+ * @param minutes
+ * @return
+ */
+ @Override
+ public List getNoPayOrderByDuration(int minutes, String paymentType) {
+
+ Instant instant = Instant.now().minus(Duration.ofMinutes(minutes));
+
+ QueryWrapper queryWrapper = new QueryWrapper<>();
+ queryWrapper.eq("order_status", OrderStatusEnum.NOT_PAY.getType());
+ queryWrapper.le("create_time", instant);
+ queryWrapper.eq("payment_type", paymentType);
+
+ List orderInfoList = baseMapper.selectList(queryWrapper);
+
+ return orderInfoList;
+ }
+
+ /**
+ * 根据订单号获取订单
+ * @param orderNo
+ * @return
+ */
+ @Override
+ public OrderInfo getOrderByOrderNo(String orderNo) {
+
+ QueryWrapper queryWrapper = new QueryWrapper<>();
+ queryWrapper.eq("order_no", orderNo);
+ OrderInfo orderInfo = baseMapper.selectOne(queryWrapper);
+
+ return orderInfo;
+ }
+
+
+ /**
+ * 根据商品id查询未支付订单
+ * 防止重复创建订单对象
+ * @param productId
+ * @return
+ */
+ private OrderInfo getNoPayOrderByProductId(Long productId, String paymentType) {
+
+ QueryWrapper queryWrapper = new QueryWrapper<>();
+ queryWrapper.eq("product_id", productId);
+ queryWrapper.eq("order_status", OrderStatusEnum.NOT_PAY.getType());
+ queryWrapper.eq("payment_type", paymentType);
+// queryWrapper.eq("user_id", userId);
+ OrderInfo orderInfo = baseMapper.selectOne(queryWrapper);
+ return orderInfo;
+ }
+}
diff --git a/src/main/java/com/ai/da/service/impl/PaymentInfoServiceImpl.java b/src/main/java/com/ai/da/service/impl/PaymentInfoServiceImpl.java
new file mode 100644
index 00000000..4bafa595
--- /dev/null
+++ b/src/main/java/com/ai/da/service/impl/PaymentInfoServiceImpl.java
@@ -0,0 +1,90 @@
+package com.ai.da.service.impl;
+
+import com.ai.da.common.enums.PayTypeEnum;
+import com.ai.da.mapper.PaymentInfoMapper;
+import com.ai.da.mapper.entity.PaymentInfo;
+import com.ai.da.service.PaymentInfoService;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.google.gson.Gson;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+
+import java.math.BigDecimal;
+import java.util.HashMap;
+import java.util.Map;
+
+@Service
+@Slf4j
+public class PaymentInfoServiceImpl extends ServiceImpl implements PaymentInfoService {
+
+ /**
+ * 记录支付日志:微信支付
+ * @param plainText
+ */
+ @Override
+ public void createPaymentInfo(String plainText) {
+
+ log.info("记录支付日志");
+
+ Gson gson = new Gson();
+ HashMap plainTextMap = gson.fromJson(plainText, HashMap.class);
+
+ //订单号
+ String orderNo = (String)plainTextMap.get("out_trade_no");
+ //业务编号
+ String transactionId = (String)plainTextMap.get("transaction_id");
+ //支付类型
+ String tradeType = (String)plainTextMap.get("trade_type");
+ //交易状态
+ String tradeState = (String)plainTextMap.get("trade_state");
+ //用户实际支付金额
+ Map amount = (Map)plainTextMap.get("amount");
+ Integer payerTotal = ((Double) amount.get("payer_total")).intValue();
+
+ PaymentInfo paymentInfo = new PaymentInfo();
+ paymentInfo.setOrderNo(orderNo);
+ paymentInfo.setPaymentType(PayTypeEnum.WXPAY.getType());
+ paymentInfo.setTransactionId(transactionId);
+ paymentInfo.setTradeType(tradeType);
+ paymentInfo.setTradeState(tradeState);
+ paymentInfo.setPayerTotal(payerTotal);
+ paymentInfo.setContent(plainText);
+
+ baseMapper.insert(paymentInfo);
+ }
+
+ /**
+ * 记录支付日志:支付宝
+ * @param params
+ */
+ @Override
+ public void createPaymentInfoForAliPay(Map params) {
+
+ log.info("记录支付日志");
+
+ //获取订单号
+ String orderNo = params.get("out_trade_no");
+ //业务编号
+ String transactionId = params.get("trade_no");
+ //交易状态
+ String tradeStatus = params.get("trade_status");
+ //交易金额
+ String totalAmount = params.get("total_amount");
+ int totalAmountInt = new BigDecimal(totalAmount).multiply(new BigDecimal("100")).intValue();
+
+
+ PaymentInfo paymentInfo = new PaymentInfo();
+ paymentInfo.setOrderNo(orderNo);
+ paymentInfo.setPaymentType(PayTypeEnum.ALIPAY.getType());
+ paymentInfo.setTransactionId(transactionId);
+ paymentInfo.setTradeType("电脑网站支付");
+ paymentInfo.setTradeState(tradeStatus);
+ paymentInfo.setPayerTotal(totalAmountInt);
+
+ Gson gson = new Gson();
+ String json = gson.toJson(params, HashMap.class);
+ paymentInfo.setContent(json);
+
+ baseMapper.insert(paymentInfo);
+ }
+}
diff --git a/src/main/java/com/ai/da/service/impl/ProductServiceImpl.java b/src/main/java/com/ai/da/service/impl/ProductServiceImpl.java
new file mode 100644
index 00000000..0aa9d4b3
--- /dev/null
+++ b/src/main/java/com/ai/da/service/impl/ProductServiceImpl.java
@@ -0,0 +1,12 @@
+package com.ai.da.service.impl;
+
+import com.ai.da.mapper.ProductMapper;
+import com.ai.da.mapper.entity.Product;
+import com.ai.da.service.ProductService;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import org.springframework.stereotype.Service;
+
+@Service
+public class ProductServiceImpl extends ServiceImpl implements ProductService {
+
+}
diff --git a/src/main/java/com/ai/da/service/impl/RefundInfoServiceImpl.java b/src/main/java/com/ai/da/service/impl/RefundInfoServiceImpl.java
new file mode 100644
index 00000000..f05dd74f
--- /dev/null
+++ b/src/main/java/com/ai/da/service/impl/RefundInfoServiceImpl.java
@@ -0,0 +1,156 @@
+package com.ai.da.service.impl;
+
+import com.ai.da.common.utils.OrderNoUtils;
+import com.ai.da.mapper.RefundInfoMapper;
+import com.ai.da.mapper.entity.OrderInfo;
+import com.ai.da.mapper.entity.RefundInfo;
+import com.ai.da.service.OrderInfoService;
+import com.ai.da.service.RefundInfoService;
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.google.gson.Gson;
+import org.springframework.stereotype.Service;
+
+import javax.annotation.Resource;
+import java.time.Duration;
+import java.time.Instant;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+@Service
+public class RefundInfoServiceImpl extends ServiceImpl implements RefundInfoService {
+
+ @Resource
+ private OrderInfoService orderInfoService;
+
+ /**
+ * 根据订单号创建退款订单
+ * @param orderNo
+ * @return
+ */
+ @Override
+ public RefundInfo createRefundByOrderNo(String orderNo, String reason) {
+
+ //根据订单号获取订单信息
+ OrderInfo orderInfo = orderInfoService.getOrderByOrderNo(orderNo);
+
+ //根据订单号生成退款订单
+ RefundInfo refundInfo = new RefundInfo();
+ refundInfo.setOrderNo(orderNo);//订单编号
+ refundInfo.setRefundNo(OrderNoUtils.getRefundNo());//退款单编号
+ refundInfo.setTotalFee(orderInfo.getTotalFee());//原订单金额(分)
+ refundInfo.setRefund(orderInfo.getTotalFee());//退款金额(分)
+ refundInfo.setReason(reason);//退款原因
+
+ //保存退款订单
+ baseMapper.insert(refundInfo);
+
+ return refundInfo;
+ }
+
+
+ /**
+ * 记录退款记录
+ * @param content
+ */
+ @Override
+ public void updateRefund(String content) {
+
+ //将json字符串转换成Map
+ Gson gson = new Gson();
+ Map resultMap = gson.fromJson(content, HashMap.class);
+
+ //根据退款单编号修改退款单
+ QueryWrapper queryWrapper = new QueryWrapper<>();
+ queryWrapper.eq("refund_no", resultMap.get("out_refund_no"));
+
+ //设置要修改的字段
+ RefundInfo refundInfo = new RefundInfo();
+
+ refundInfo.setRefundId(resultMap.get("refund_id"));//微信支付退款单号
+
+ //查询退款和申请退款中的返回参数
+ if(resultMap.get("status") != null){
+ refundInfo.setRefundStatus(resultMap.get("status"));//退款状态
+ refundInfo.setContentReturn(content);//将全部响应结果存入数据库的content字段
+ }
+ //退款回调中的回调参数
+ if(resultMap.get("refund_status") != null){
+ refundInfo.setRefundStatus(resultMap.get("refund_status"));//退款状态
+ refundInfo.setContentNotify(content);//将全部响应结果存入数据库的content字段
+ }
+
+ //更新退款单
+ baseMapper.update(refundInfo, queryWrapper);
+ }
+
+ /**
+ * 找出申请退款超过minutes分钟并且未成功的退款单
+ * @param minutes
+ * @return
+ */
+ @Override
+ public List getNoRefundOrderByDuration(int minutes) {
+
+ //minutes分钟之前的时间
+ Instant instant = Instant.now().minus(Duration.ofMinutes(minutes));
+
+ QueryWrapper queryWrapper = new QueryWrapper<>();
+// queryWrapper.eq("refund_status", WxRefundStatus.PROCESSING.getType());
+ queryWrapper.le("create_time", instant);
+ List refundInfoList = baseMapper.selectList(queryWrapper);
+ return refundInfoList;
+ }
+
+ /**
+ * 根据订单号创建退款订单
+ * @param orderNo
+ * @return
+ */
+ @Override
+ public RefundInfo createRefundByOrderNoForAliPay(String orderNo, String reason) {
+
+ //根据订单号获取订单信息
+ OrderInfo orderInfo = orderInfoService.getOrderByOrderNo(orderNo);
+
+ //根据订单号生成退款订单
+ RefundInfo refundInfo = new RefundInfo();
+ refundInfo.setOrderNo(orderNo);//订单编号
+ refundInfo.setRefundNo(OrderNoUtils.getRefundNo());//退款单编号
+
+ refundInfo.setTotalFee(orderInfo.getTotalFee());//原订单金额(分)
+ refundInfo.setRefund(orderInfo.getTotalFee());//退款金额(分)
+ refundInfo.setReason(reason);//退款原因
+
+ //保存退款订单
+ baseMapper.insert(refundInfo);
+
+ return refundInfo;
+ }
+
+ /**
+ * 更新退款记录
+ * @param refundNo
+ * @param content
+ * @param refundStatus
+ */
+ @Override
+ public void updateRefundForAliPay(String refundNo, String content, String refundStatus) {
+
+ //根据退款单编号修改退款单
+ QueryWrapper queryWrapper = new QueryWrapper<>();
+ queryWrapper.eq("refund_no", refundNo);
+
+ //设置要修改的字段
+ RefundInfo refundInfo = new RefundInfo();
+ refundInfo.setRefundStatus(refundStatus);//退款状态
+ refundInfo.setContentReturn(content);//将全部响应结果存入数据库的content字段
+
+ //更新退款单
+ baseMapper.update(refundInfo, queryWrapper);
+
+ }
+
+}
diff --git a/src/main/resources/alipay-sandbox.properties b/src/main/resources/alipay-sandbox.properties
new file mode 100644
index 00000000..972660de
--- /dev/null
+++ b/src/main/resources/alipay-sandbox.properties
@@ -0,0 +1,38 @@
+# 支付宝支付相关参数
+
+# 应用ID,您的APPID,收款账号既是您的APPID对应支付宝账号
+#alipay.app-id=2021000119635499
+alipay.app-id=9021000134653815
+
+# 商户PID,卖家支付宝账号ID
+#alipay.seller-id=2088621957993562
+alipay.seller-id=2088721029614572
+
+# 支付宝网关
+#alipay.gateway-url=https://openapi.alipaydev.com/gateway.do
+alipay.gateway-url=https://openapi-sandbox.dl.alipaydev.com/gateway.do
+
+# 商户私钥,您的PKCS8格式RSA2私钥
+#alipay.merchant-private-key=MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCJPnt6TMZ1A06SMeNxQT0WhGbRd74JBCcdqQt4POzBMJ1NE6U/OiG2xZEnUqYWC2ukkOgZZEvTuWDI9q2aEFN7E2Fcj39JCwRmE0an153JIOkb9L2nngTsAAN7cwLZZ6/dAPnrZjtN0f/HRVBXPSNLBdpoS/pbKRurM2NccZkbTZtluCdt4IvBBjjcV3DJOCYP9yrLTP3HYDxep3HrZCvXuR2Iseb+c6qc4IF/2UKQTz+iavxCy3zJTYbDzD0cL7yC4HmSD7vbNGZvkzZB1RNWt0NILj2LdFG10T7zZahN461FiYozRfD7LDPXqq+uuZMM5i0jpXVrs2MDk6HeS0lJAgMBAAECggEAF87wCrpQ3zGwqqne4+HGYCad046rN9MxmfKeW8Bt7eGqGBnlW7+Q460ITkMHLuHSTZ0ZtnXwtYz+Hj60xPo6ESq+hBkcoqY3oCGN60X7SE3eQoxFblN6VRp3gC3me6KCHpuxv0Vf2lMoxP/gPRINElG0ns03ZCMQerWSchH+1n5xUX/SrsgYDLaHfCxSpGsI/iyjTHXl+KqZeiFoRY+0tlJCTsc6P8JBYEeI5l8Iza/CjxDgFT41B4RksMw9ZEUCwxMiQhqIOThdTxtpA+MpUjoizhngq1xAXMcEz3QlnV7V2icyWjDAAz7bMCISUBa2MLkNWb392/yROKCBcXKYZQKBgQDY5T8QbC3GiUw0IQrVwm1A46zBzMDuQV+a4/2q63f8BBRZcbOluzxuCSfsTIVFKF1eJqpD75+76rB2z39P5xSS2/9SuO9FofV5iUDZuC93mOvR5nwh0rkgizhes81p+i7S9VcQcLTM/gK8ta3VXC3Pv/9bIGlTyFPLS0iMLUJlywKBgQCh/PIjoBFqKWpB6wSTn8hotvvE0WU1XMhm09WnlrMN2O+TuL71PoVS5vhCBgCJW5e3OOOS1K8uYLWaAQY9g/PvzYWLKF7CkCZNTvdds67QMreU9Cfm3jOwUkOibXarJEwY0l52k4xihQ6o8QGvG6XmNw3oq3RX/t/Y0DC84lMKuwKBgQCBQnYIAoBxToe0lXCQnfNgdY8SXEUqeJlShMc7YmM6NPAvsfxfK6vC4///6kaORZUHNEHKhPcMFbyeweBcrRlswGF0WjR2qiPSD4MvfX4EZ4U6rYKS4bNkerPYdI1ZuDjJjl8ZtCF7/XGCJz/25J2Eryauly1OOhf+Etqkd6CXawKBgDlmf4seMm2TBWMcW3/QM9zfUnHY3Ws+WIkPcXs0THiQsbx/z7Lpl6bbz4bdx5zkxusXDpU+JmFhxZgv2r07n9oO0s6P3JxHJjtoywD6Je0Cu8jdh7IodNp7HBpXfaCBeTGmgfC0sh9LFPnKhRU+z9e3FIepEc4Is9uJUmvsKw73AoGAM44/Oz7axzcVEIaD/7jyy1GiaYCmI55qqjocXfbK9QlWDiLnGfbx2UB6BC1y7WD6a9bMpatLXppTDEL+qHX0jUp0u06LQnJi9SAUBcff4LioSJxBrYx6ovYjAEmpoyiW/AAQBJ1oyp1UvpX0avfMa0Hdo1e+YnFEZluCr184uns=
+alipay.merchant-private-key=MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCS8D3aAKvh+juT+5GEXkbKsHtoSK8odQ3htEH3XE+TP5PiXiOiv7pCnVDaIJ5gxfVBeQNC/D7E0zD7J+F1pRFtXA2MQ/ZMXd21hCjkXjIdBBo7iprPKD51AQIHJ8ocjKH0AnCcWAKPN03HvwCbPyYVXHcGO7XrIE2XFgTszsJYOw/dvruVxVF/OEXtVmgOJYIbHQnd+EfJ68BbMWbebzNBgy2YN/EBJldMMoaFcwtei7wCvUcNxWWqaus+n5u9+xFzPX/5495F3clu9yDydIdmvoRYEMdlzb0PbEzGg1KAaIHb8weNVd0bypOTkVK4Y/2tkPfAAv1hF4e6Uf2MAFw5AgMBAAECggEAc1/mAP4T+F0mWLzodrob9dz7GVgxCKBYpQeH4HWAGguKz+zBKHJJCzNBH/UKxsvJFn6xe/BaTOvTAvI+isRw2aC1FpTqmO3/Px6liSMZUEU/X88mXVCpMe/3qqyoeyBFrjYYO/qzD8cJ8LReAohkG9O9tYw+/H8hzWdBuLtkSCft1iHMUFT9M78lHgHIidUZHio+ga55yLEzx8iLibKXeRTFBAtrzknP0SWO25yHtPuPQhmAF8MGDzACm7fFOsccqBH11EmJGUlMAygT89bWXkWSGtPtbmprNe1lVsg0BlyZdgts5qHIrQYAEr/8o/MZI1d3dXv0UC4YTbA1ibaDsQKBgQDWui+5fJ/Xxc30XsSqGU9mgmM2Qf6PrriixTWfbe0Xdw090D+n4zjyfHOjZCEchFbv1FaDNpTt04IZ0bt2Fx6TwhHFxZnIWAMuLLt0ei4A/iCgTfkCrSwvhJ5uuAA5wFDTJIfyQ+JOpk79BVXm5uk2WuvXWTZXIcz2gA4E8PPnJQKBgQCvLnSQkmWCRv9TU8Mk9lXpWruQf8c3oVxvJ0CFDJK3+33PswiZTX+jcymAvK6kd1H0Mg8jJvH+uyeNPyF7aQW8NHasejqRNhyGyj1ZhRTuDIDGs2qIkZv1RAoDB12T0zATMeoJRbycQpOvi6FGI6KkOmjbgVzrrOPLdYh7wKxOhQKBgBg9MEH6BXkwBQ2+3Ohurv3dXiGz1W6WxJTrOiBjIk5xy/bcWTM0fSsyTUN2fB5h+gZcq99n++vU4mzTthughH0dfTAcHD33YXSG7Co72JGYHkq6Qo/AyKociF3rO2tJgFY1W3tHmnZSY7xAK/BCxLS0EsdedLnlQOPalLqOEJpZAoGAOFCq0D7/ut2v/s2SkjNOcvHkEhPBqzSIToNDzoG4OMT0MOzQ+tdbDORclzIQwnZJNkNRcm92o/juQg3laZLdcR88dz3jzgKjMQGar0iE7fG/BCFxQe3tQO9aeJbJklf1wHXvUoPEmkccyvYMJkwWYM0WIFp50JmHsURbV80clzECgYEAjJdoYgyJSKyuOSJQ8DSyAGGmUZdUILNDZl3KTYWAlPySmDqtqRb0nHpr/iOSlCuazEfOm3CnKaNqa7lx+Fyp3JYdYKa218crif4mjAnEoINcxTrCFVCe+bQqj2oYNi+rSptO6ingTUMv9HPjz0U2DhBRFI/pLPyQnOZx70hOCyQ=
+
+# 支付宝公钥,查看地址:https://openhome.alipay.com/platform/keyManage.htm 对应APPID下的支付宝公钥
+#alipay.alipay-public-key=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvKoCpGtG31iY2Llj3t8MKRpaVCntnalWmXbKcHCiaYcUUjzcMPH3/tOr5ORK5W1NjuLu9uRrsXqROBmQYN+0y4nea+fU989i2IxtOGR/h2Kvhyyk/lPjNkmgz7K8VqbGGeVzTadPSK49FcrDVEshJ6C92vEKq6TmUfhKgCLiLZ288fHBDRvzUnoj8O/LBXiDroq1zX+DXYCHcQeFljkF5ivyxDZBkWl23hYTtnFClvN5lVLK4d294wyprF7IMv3XhQOfJS+Pr527CsfwT0JE44lyidCvslvn9DSdHdIfTIzHDsIEiDzB/OvRCbwpEA7UGnWQxQf7AusX2TTRQHWW7wIDAQAB
+alipay.alipay-public-key=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAhDXg0niJ+GSB4VRvPJqzgDhNCP68TRZRLSKLCr/cw6qy911UxRNS96f/ALE7TOLYEsqsolV1inrJ1PU3+BZY9LiKthzEJhdVNzEwXtQrZVknRt3rDtWM2PkKanlQt9LN1Kr28eZ1jJipbkyTLBZOR5RxQ6M6BW0hewrMq/aJLzugP6I7pFHy2iF9zmIm0upy3bUiO5kJwCCXAXfKTG4bLPzhMZCWmDipuscnvR/TN2Nka3WGAJVpiUuRo/poxWKlQpKl6K9rz5RypiAJSaW8zacNyBZ45xIoep3d48v/izOSq2NxLDs6oMUb/BwpJRgfex6dOrnqyIXy0+i9DwEDEwIDAQAB
+
+# 接口内容加密秘钥,对称秘钥
+#alipay.content-key=D8entyfafkkFwtMbUqj3Mw==
+alipay.content-key=hKZgxtPGmOivCphtHM5gNg==
+
+# 页面跳转同步通知页面路径
+alipay.return-url=http://localhost:8080/#/success
+
+# 服务器异步通知页面路径 需http://格式的完整路径,不能加?id=123这类自定义参数,必须外网可以正常访问
+# 注意:每次重新启动ngrok,都需要根据实际情况修改这个配置
+#alipay.notify-url=https://77ea-221-239-177-21.ngrok.io/api/ali-pay/trade/notify
+alipay.notify-url=https://03a3-117-143-125-44.ngrok-free.app/api/ali-pay/trade/notify
+
+
+
+
+