diff --git a/client-api/src/main/java/org/jiayunet/pojo/dto/memberProduct/CreateOrderDto.java b/client-api/src/main/java/org/jiayunet/pojo/dto/memberProduct/CreateOrderDto.java
index 0dfd6b3..69f89f1 100644
--- a/client-api/src/main/java/org/jiayunet/pojo/dto/memberProduct/CreateOrderDto.java
+++ b/client-api/src/main/java/org/jiayunet/pojo/dto/memberProduct/CreateOrderDto.java
@@ -13,6 +13,9 @@ public class CreateOrderDto {
/** 订单ID,用于轮询状态 */
private Long orderId;
- /** 微信支付二维码链接 */
- private String codeUrl;
+ /** 支付渠道 1=微信 2=支付宝 */
+ private Integer payChannel;
+
+ /** 支付数据,微信=二维码URL,支付宝=HTML表单,前端根据payChannel判断渲染方式 */
+ private String payData;
}
diff --git a/client-api/src/main/java/org/jiayunet/service/AlipayNotifyMessageAbstractImpl.java b/client-api/src/main/java/org/jiayunet/service/AlipayNotifyMessageAbstractImpl.java
new file mode 100644
index 0000000..1cbafcf
--- /dev/null
+++ b/client-api/src/main/java/org/jiayunet/service/AlipayNotifyMessageAbstractImpl.java
@@ -0,0 +1,77 @@
+package org.jiayunet.service;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
+import lombok.AllArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.jiayunet.alipay.AlipayNotifyMessageAbstract;
+import org.jiayunet.mapper.PayAlipayFlowMapper;
+import org.jiayunet.pojo.po.PayAlipayFlow;
+import org.springframework.context.annotation.Primary;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.time.Instant;
+import java.util.Map;
+
+/**
+ * 支付宝支付回调实现
+ *
依赖:MemberProductService(会员订单处理)、PayAlipayFlowMapper(更新支付流水)
+ * 使用表:bg_pay_alipay_flow(更新流水状态)、bg_member_order(通过MemberProductService处理)
+ *
+ * @author zk
+ */
+@Service
+@Primary
+@Slf4j
+@AllArgsConstructor
+public class AlipayNotifyMessageAbstractImpl implements AlipayNotifyMessageAbstract {
+
+ private final MemberProductService memberProductService;
+ private final PayAlipayFlowMapper payAlipayFlowMapper;
+
+ /**
+ * 支付回调
+ * 1. 判断流水状态防重复处理 2. 更新支付宝流水 3. 调用公共订单处理逻辑
+ */
+ @Override
+ @Transactional(rollbackFor = Exception.class)
+ public void payMessageHandle(Map params) {
+ String outTradeNo = params.get("out_trade_no");
+ String tradeNo = params.get("trade_no");
+ String tradeStatus = params.get("trade_status");
+
+ log.info("支付宝回调 outTradeNo:{} tradeNo:{} tradeStatus:{}", outTradeNo, tradeNo, tradeStatus);
+
+ // 只处理支付成功
+ if (!"TRADE_SUCCESS".equals(tradeStatus) && !"TRADE_FINISHED".equals(tradeStatus)) {
+ log.info("支付宝回调非成功状态,跳过 outTradeNo:{} tradeStatus:{}", outTradeNo, tradeStatus);
+ return;
+ }
+
+ // 1. 查流水,已处理则直接返回(防重复回调)
+ PayAlipayFlow flow = payAlipayFlowMapper.selectOne(
+ new LambdaQueryWrapper().eq(PayAlipayFlow::getOrderNo, outTradeNo)
+ );
+ if (flow == null) {
+ log.warn("支付宝回调流水不存在 outTradeNo:{}", outTradeNo);
+ return;
+ }
+ if (flow.getStatus() != 0) {
+ log.info("支付宝流水已处理,跳过 outTradeNo:{} status:{}", outTradeNo, flow.getStatus());
+ return;
+ }
+
+ // 2. 更新支付宝流水
+ payAlipayFlowMapper.update(null, new LambdaUpdateWrapper()
+ .eq(PayAlipayFlow::getId, flow.getId())
+ .eq(PayAlipayFlow::getStatus, 0)
+ .set(PayAlipayFlow::getStatus, 1)
+ .set(PayAlipayFlow::getTradeNo, tradeNo)
+ .set(PayAlipayFlow::getSuccessTime, Instant.now())
+ .set(PayAlipayFlow::getNotifyData, params.toString()));
+
+ // 3. 调用公共订单处理(更新订单状态 + 发放权限)
+ memberProductService.handlePaySuccess(outTradeNo);
+ }
+}
diff --git a/client-api/src/main/java/org/jiayunet/service/MemberProductService.java b/client-api/src/main/java/org/jiayunet/service/MemberProductService.java
index 14ae528..229cba1 100644
--- a/client-api/src/main/java/org/jiayunet/service/MemberProductService.java
+++ b/client-api/src/main/java/org/jiayunet/service/MemberProductService.java
@@ -13,6 +13,7 @@ import org.jiayunet.pojo.dto.memberProduct.OrderDetailDto;
import org.jiayunet.pojo.po.*;
import org.jiayunet.tool.UserSecurityTool;
import org.jiayunet.wxPay.WxNativePayAbility;
+import org.jiayunet.alipay.AlipayPagePayAbility;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
@@ -61,6 +62,12 @@ public class MemberProductService {
@Autowired
private WxNativePayAbility wxNativePayAbility;
+ @Autowired
+ private AlipayPagePayAbility alipayPagePayAbility;
+
+ @Autowired
+ private PayAlipayFlowMapper payAlipayFlowMapper;
+
@Value("${wx_pay.merchant_id:}")
private String merchantId;
@@ -109,19 +116,19 @@ public class MemberProductService {
.eq(MemberOrder::getId, order.getId())
.set(MemberOrder::getOrderNo, orderNo));
- // 4. 调支付渠道下单,拿二维码URL
- String codeUrl;
+ // 4. 调支付渠道下单
+ String payData;
if (payChannel == 1) {
- codeUrl = prepayWechat(orderNo, product);
+ payData = prepayWechat(orderNo, product);
} else {
- // TODO 支付宝当面付
- codeUrl = prepayAlipay(orderNo, product);
+ payData = prepayAlipay(orderNo, product);
}
// 5. 返回
CreateOrderDto dto = new CreateOrderDto();
dto.setOrderId(order.getId());
- dto.setCodeUrl(codeUrl);
+ dto.setPayChannel(payChannel);
+ dto.setPayData(payData);
return dto;
}
@@ -218,11 +225,22 @@ public class MemberProductService {
}
/**
- * 支付宝当面付下单
- * TODO 接入支付宝当面付SDK,返回二维码URL
+ * 支付宝电脑网站支付下单
*/
private String prepayAlipay(String orderNo, MemberProduct product) {
- throw new BusinessException(BusinessExpCodeEnum.UNKNOWN_ERROR, "支付宝支付暂未开放");
+ // 创建支付宝流水
+ PayAlipayFlow flow = new PayAlipayFlow();
+ flow.setOrderType("member");
+ flow.setOrderNo(orderNo);
+ flow.setTotal(product.getPrice());
+ flow.setStatus(0);
+ payAlipayFlowMapper.insert(flow);
+
+ // 金额转元(分→元)
+ String totalAmount = String.format("%.2f", product.getPrice() / 100.0);
+
+ // 调支付宝下单,返回HTML表单
+ return alipayPagePayAbility.prepay(orderNo, totalAmount, product.getProductName());
}
/**
diff --git a/client-api/src/main/resources/application-local.yml b/client-api/src/main/resources/application-local.yml
index 7c6d172..cf66511 100644
--- a/client-api/src/main/resources/application-local.yml
+++ b/client-api/src/main/resources/application-local.yml
@@ -33,6 +33,15 @@ wx_pay:
#回调域名地址
notify_domain: ${API_NOTIFY_DOMAIN:http://127.0.0.1:8080/api/}
+# 支付宝支付
+alipay:
+ # status close:关闭 open:打开
+ status: close
+ app_id: ${ALIPAY_APP_ID:your_app_id}
+ private_key: ${ALIPAY_PRIVATE_KEY:your_private_key}
+ alipay_public_key: ${ALIPAY_PUBLIC_KEY:your_alipay_public_key}
+ notify_domain: ${API_NOTIFY_DOMAIN:http://127.0.0.1:8080/api/}
+
app:
# 加密秘钥配置
secret:
diff --git a/common/pom.xml b/common/pom.xml
index 7579879..e4d1821 100644
--- a/common/pom.xml
+++ b/common/pom.xml
@@ -63,6 +63,11 @@
wechatpay-java
0.2.15
+
+ com.alipay.sdk
+ alipay-sdk-java
+ 4.40.791.ALL
+
com.aliyun
dysmsapi20170525
diff --git a/common/src/main/java/org/jiayunet/alipay/AlipayClientConfig.java b/common/src/main/java/org/jiayunet/alipay/AlipayClientConfig.java
new file mode 100644
index 0000000..d89886d
--- /dev/null
+++ b/common/src/main/java/org/jiayunet/alipay/AlipayClientConfig.java
@@ -0,0 +1,41 @@
+package org.jiayunet.alipay;
+
+import com.alipay.api.AlipayClient;
+import com.alipay.api.AlipayConfig;
+import com.alipay.api.DefaultAlipayClient;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+/**
+ * 支付宝客户端配置
+ *
+ * @author zk
+ */
+@Configuration
+@ConditionalOnProperty(name = "alipay.status", havingValue = "open")
+public class AlipayClientConfig {
+
+ @Value("${alipay.app_id}")
+ private String appId;
+
+ @Value("${alipay.private_key}")
+ private String privateKey;
+
+ @Value("${alipay.alipay_public_key}")
+ private String alipayPublicKey;
+
+ @Bean
+ public AlipayClient alipayClient() throws Exception {
+ AlipayConfig config = new AlipayConfig();
+ config.setServerUrl("https://openapi.alipay.com/gateway.do");
+ config.setAppId(appId);
+ config.setPrivateKey(privateKey);
+ config.setFormat("json");
+ config.setAlipayPublicKey(alipayPublicKey);
+ config.setCharset("UTF-8");
+ config.setSignType("RSA2");
+ return new DefaultAlipayClient(config);
+ }
+}
diff --git a/common/src/main/java/org/jiayunet/alipay/AlipayNotifyController.java b/common/src/main/java/org/jiayunet/alipay/AlipayNotifyController.java
new file mode 100644
index 0000000..31167d0
--- /dev/null
+++ b/common/src/main/java/org/jiayunet/alipay/AlipayNotifyController.java
@@ -0,0 +1,74 @@
+package org.jiayunet.alipay;
+
+import com.alipay.api.AlipayApiException;
+import com.alipay.api.internal.util.AlipaySignature;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+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;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * 支付宝支付回调入口
+ *
+ * @author zk
+ */
+@RestController
+@ConditionalOnProperty(name = "alipay.status", havingValue = "open")
+@RequestMapping("/public/alipayNotify")
+@Slf4j
+public class AlipayNotifyController {
+
+ @Value("${alipay.alipay_public_key}")
+ private String alipayPublicKey;
+
+ @Autowired
+ private AlipayNotifyMessageAbstract alipayNotifyMessageAbstract;
+
+ /**
+ * 支付结果回调
+ */
+ @PostMapping("/pay")
+ public String pay(HttpServletRequest request) {
+ // 1. 获取所有回调参数
+ Map params = new HashMap<>();
+ Map requestParams = request.getParameterMap();
+ for (String name : requestParams.keySet()) {
+ String[] values = requestParams.get(name);
+ String valueStr = String.join(",", values);
+ params.put(name, valueStr);
+ }
+
+ log.info("收到支付宝支付回调数据:{}", params);
+
+ // 2. SDK验签
+ boolean signVerified;
+ try {
+ signVerified = AlipaySignature.rsaCheckV1(params, alipayPublicKey, "UTF-8", "RSA2");
+ } catch (AlipayApiException e) {
+ log.error("支付宝回调验签异常", e);
+ return "failure";
+ }
+
+ if (!signVerified) {
+ log.error("支付宝回调验签失败 params:{}", params);
+ return "failure";
+ }
+
+ // 3. 验签通过,处理业务
+ try {
+ alipayNotifyMessageAbstract.payMessageHandle(params);
+ } catch (Exception e) {
+ log.error("支付宝回调业务处理异常", e);
+ return "failure";
+ }
+
+ return "success";
+ }
+}
diff --git a/common/src/main/java/org/jiayunet/alipay/AlipayNotifyMessageAbstract.java b/common/src/main/java/org/jiayunet/alipay/AlipayNotifyMessageAbstract.java
new file mode 100644
index 0000000..d58d5dc
--- /dev/null
+++ b/common/src/main/java/org/jiayunet/alipay/AlipayNotifyMessageAbstract.java
@@ -0,0 +1,21 @@
+package org.jiayunet.alipay;
+
+import java.util.Map;
+
+/**
+ * 支付宝支付回调消息处理接口
+ * 使用前需要实现接口
+ *
+ * @author zk
+ */
+public interface AlipayNotifyMessageAbstract {
+
+ /**
+ * 支付结果回调消息处理
+ *
+ * @param params 回调参数(已验签)
+ */
+ default void payMessageHandle(Map params) {
+ System.out.println("支付宝支付的支付结果回调消息未处理");
+ }
+}
diff --git a/common/src/main/java/org/jiayunet/alipay/AlipayPagePayAbility.java b/common/src/main/java/org/jiayunet/alipay/AlipayPagePayAbility.java
new file mode 100644
index 0000000..2216ed5
--- /dev/null
+++ b/common/src/main/java/org/jiayunet/alipay/AlipayPagePayAbility.java
@@ -0,0 +1,61 @@
+package org.jiayunet.alipay;
+
+import com.alipay.api.AlipayApiException;
+import com.alipay.api.AlipayClient;
+import com.alipay.api.domain.AlipayTradePagePayModel;
+import com.alipay.api.request.AlipayTradePagePayRequest;
+import com.alipay.api.response.AlipayTradePagePayResponse;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.stereotype.Component;
+
+/**
+ * 支付宝电脑网站支付能力
+ *
+ * @author zk
+ */
+@ConditionalOnProperty(name = "alipay.status", havingValue = "open")
+@Component
+@Slf4j
+public class AlipayPagePayAbility {
+
+ @Autowired
+ private AlipayClient alipayClient;
+
+ @Value("${alipay.notify_domain}")
+ private String notifyDomain;
+
+ /**
+ * 电脑网站支付下单
+ *
+ * @param outTradeNo 商户订单号
+ * @param totalAmount 金额(元),如 "39.00"
+ * @param subject 订单标题
+ * @return HTML表单字符串,前端直接渲染(内嵌二维码)
+ */
+ public String prepay(String outTradeNo, String totalAmount, String subject) {
+ AlipayTradePagePayRequest request = new AlipayTradePagePayRequest();
+ request.setNotifyUrl(notifyDomain + "/public/alipayNotify/pay");
+ log.info("以默认设置支付宝回调地址为:{}", request.getNotifyUrl());
+
+ AlipayTradePagePayModel model = new AlipayTradePagePayModel();
+ model.setOutTradeNo(outTradeNo);
+ model.setTotalAmount(totalAmount);
+ model.setSubject(subject);
+ model.setProductCode("FAST_INSTANT_TRADE_PAY");
+ model.setQrPayMode("4");
+ model.setQrcodeWidth(200L);
+
+ request.setBizModel(model);
+
+ try {
+ AlipayTradePagePayResponse response = alipayClient.pageExecute(request, "POST");
+ return response.getBody();
+ } catch (AlipayApiException e) {
+ log.error("支付宝下单异常 outTradeNo:{} error:{}", outTradeNo, e.getMessage());
+ }
+ throw new RuntimeException("支付宝下单异常");
+ }
+}
diff --git a/manager/src/main/java/org/jiayunet/mapper/PayAlipayFlowMapper.java b/manager/src/main/java/org/jiayunet/mapper/PayAlipayFlowMapper.java
new file mode 100644
index 0000000..e15d4df
--- /dev/null
+++ b/manager/src/main/java/org/jiayunet/mapper/PayAlipayFlowMapper.java
@@ -0,0 +1,14 @@
+package org.jiayunet.mapper;
+
+import org.apache.ibatis.annotations.Mapper;
+import org.jiayunet.pojo.po.PayAlipayFlow;
+
+/**
+ * 支付宝支付流水 Mapper
+ *
+ * @author zk
+ */
+@Mapper
+public interface PayAlipayFlowMapper extends CommonMapper {
+
+}
diff --git a/manager/src/main/java/org/jiayunet/pojo/po/PayAlipayFlow.java b/manager/src/main/java/org/jiayunet/pojo/po/PayAlipayFlow.java
new file mode 100644
index 0000000..acc2af6
--- /dev/null
+++ b/manager/src/main/java/org/jiayunet/pojo/po/PayAlipayFlow.java
@@ -0,0 +1,49 @@
+package org.jiayunet.pojo.po;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+
+import java.time.Instant;
+
+/**
+ * 支付宝支付流水表(bg_pay_alipay_flow)
+ * 记录支付宝支付的下单和回调信息
+ *
+ * @author zk
+ */
+@Data
+@TableName(value = "bg_pay_alipay_flow")
+public class PayAlipayFlow {
+
+ @TableId(type = IdType.ASSIGN_ID)
+ private Long id;
+
+ /** 订单类型,member=会员订单,后续可扩展其他业务类型 */
+ private String orderType;
+
+ /** 商户订单号,对应支付宝 out_trade_no,关联业务订单表 */
+ private String orderNo;
+
+ /** 支付宝交易号,支付成功后由回调返回 */
+ private String tradeNo;
+
+ /** 订单金额(分) */
+ private Integer total;
+
+ /** 流水状态 0=待支付 1=已支付 2=已关闭 */
+ private Integer status;
+
+ /** 支付成功时间,系统收到成功回调的时间 */
+ private Instant successTime;
+
+ /** 回调原始参数完整数据,用于问题排查 */
+ private String notifyData;
+
+ /** 记录创建时间(下单时写入) */
+ private Instant createTime;
+
+ /** 记录更新时间(回调时更新) */
+ private Instant updateTime;
+}