4 İşlemeler 824c338ac1 ... 0033623835

Yazar SHA1 Mesaj Tarih
  刘云鑫 0033623835 feat:支付宝支付 1 hafta önce
  lutong b550b00aeb 商户评论的时候 显示商户的商铺名称 1 hafta önce
  fcw 97813ad061 fix(payment): 修复支付宝直付通订单ID设置问题 1 hafta önce
  zhangchen 7a3db463c5 个人设置,调整通知发送代码 1 hafta önce
17 değiştirilmiş dosya ile 1936 ekleme ve 4 silme
  1. 60 0
      alien-entity/src/main/java/shop/alien/entity/store/LifeUserPushDevice.java
  2. 60 0
      alien-entity/src/main/java/shop/alien/entity/store/PushDeviceOwnerType.java
  3. 2 0
      alien-entity/src/main/java/shop/alien/entity/store/vo/CommonCommentVo.java
  4. 2 0
      alien-entity/src/main/java/shop/alien/mapper/CommonCommentMapper.java
  5. 7 0
      alien-entity/src/main/java/shop/alien/mapper/LifeUserPushDeviceMapper.java
  6. 1 1
      alien-lawyer/src/main/java/shop/alien/lawyer/service/impl/LawyerConsultationOrderServiceImpl.java
  7. 47 0
      alien-store/src/main/java/shop/alien/store/config/UniPushProperties.java
  8. 62 0
      alien-store/src/main/java/shop/alien/store/controller/LifeUserPushDeviceController.java
  9. 26 0
      alien-store/src/main/java/shop/alien/store/dto/LifeUserPushBindDto.java
  10. 129 2
      alien-store/src/main/java/shop/alien/store/service/LifeCommentService.java
  11. 46 0
      alien-store/src/main/java/shop/alien/store/service/LifeUserPersonalizationSettingService.java
  12. 34 0
      alien-store/src/main/java/shop/alien/store/service/LifeUserPushDeviceService.java
  13. 106 1
      alien-store/src/main/java/shop/alien/store/service/LifeUserService.java
  14. 82 0
      alien-store/src/main/java/shop/alien/store/service/UniPushCloudInvokeService.java
  15. 801 0
      alien-store/src/main/java/shop/alien/store/service/impl/AlipayZftOnboardingServiceImpl.java
  16. 194 0
      alien-store/src/main/java/shop/alien/store/service/impl/LifeUserPushDeviceServiceImpl.java
  17. 277 0
      alien-store/src/main/java/shop/alien/store/strategy/payment/impl/AlipayPartnerPaymentStrategyImpl.java

+ 60 - 0
alien-entity/src/main/java/shop/alien/entity/store/LifeUserPushDevice.java

@@ -0,0 +1,60 @@
+package shop.alien.entity.store;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.io.Serializable;
+import java.util.Date;
+
+/**
+ * 推送设备 cid 绑定;{@link #userId} 含义由 {@link #ownerType} 决定。
+ */
+@Data
+@JsonInclude
+@TableName("life_user_push_device")
+@ApiModel(value = "LifeUserPushDevice", description = "用户推送设备绑定")
+public class LifeUserPushDevice implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    @ApiModelProperty(value = "主键")
+    @TableId(value = "id", type = IdType.AUTO)
+    private Integer id;
+
+    @ApiModelProperty(value = "业务用户主键:owner_type=user 时为 life_user.id;store 为 store_user.id;lawyer 为 lawyer_user.id")
+    @TableField("user_id")
+    private Integer userId;
+
+    @ApiModelProperty(value = "归属类型:user / store / lawyer(与 JWT userType 一致)")
+    @TableField("owner_type")
+    private String ownerType;
+
+    @ApiModelProperty(value = "uni-push client id")
+    @TableField("push_client_id")
+    private String pushClientId;
+
+    @ApiModelProperty(value = "平台:ios、android 等")
+    @TableField("platform")
+    private String platform;
+
+    @ApiModelProperty(value = "DCloud appid,如 __UNI__xxxx")
+    @TableField("dcloud_app_id")
+    private String dcloudAppId;
+
+    @ApiModelProperty(value = "创建时间")
+    @TableField("created_time")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private Date createdTime;
+
+    @ApiModelProperty(value = "更新时间")
+    @TableField("updated_time")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private Date updatedTime;
+}

+ 60 - 0
alien-entity/src/main/java/shop/alien/entity/store/PushDeviceOwnerType.java

@@ -0,0 +1,60 @@
+package shop.alien.entity.store;
+
+/**
+ * 推送设备绑定归属类型,与 JWT 中 userType 及表 life_user_push_device.owner_type 一致。
+ * <ul>
+ *   <li>{@link #USER} — C 端 life_user.id</li>
+ *   <li>{@link #STORE} — 门店 store_user.id</li>
+ *   <li>{@link #LAWYER} — 律师 lawyer_user.id</li>
+ * </ul>
+ */
+public enum PushDeviceOwnerType {
+
+    USER("user"),
+    STORE("store"),
+    LAWYER("lawyer");
+
+    private final String code;
+
+    PushDeviceOwnerType(String code) {
+        this.code = code;
+    }
+
+    public String getCode() {
+        return code;
+    }
+
+    /**
+     * 根据登录 token 中的 userType 解析;不支持的类型返回 null。
+     */
+    public static PushDeviceOwnerType fromJwtUserType(String jwtUserType) {
+        if (jwtUserType == null || jwtUserType.trim().isEmpty()) {
+            return USER;
+        }
+        String t = jwtUserType.trim().toLowerCase();
+        switch (t) {
+            case "user":
+                return USER;
+            case "store":
+            case "merchant":
+                return STORE;
+            case "lawyer":
+                return LAWYER;
+            default:
+                return null;
+        }
+    }
+
+    public static PushDeviceOwnerType fromCode(String code) {
+        if (code == null || code.trim().isEmpty()) {
+            return null;
+        }
+        String c = code.trim().toLowerCase();
+        for (PushDeviceOwnerType v : values()) {
+            if (v.code.equals(c)) {
+                return v;
+            }
+        }
+        return null;
+    }
+}

+ 2 - 0
alien-entity/src/main/java/shop/alien/entity/store/vo/CommonCommentVo.java

@@ -24,4 +24,6 @@ public class CommonCommentVo extends CommonComment {
     private Integer commentCount;
     @ApiModelProperty(value = "评论用户手机号")
     private String headPhone;
+    @ApiModelProperty(value = "商铺名称(评论主体为商户时有效,comment_type=2)")
+    private String storeName;
 }

+ 2 - 0
alien-entity/src/main/java/shop/alien/mapper/CommonCommentMapper.java

@@ -36,9 +36,11 @@ public interface CommonCommentMapper extends BaseMapper<CommonComment> {
             "IF(cc.comment_type = 1, lu.user_image, su.head_img) AS headImg, " +
             "IF(cc.comment_type = 1, lu.user_name, su.nick_name) AS headName, " +
             "IF(cc.comment_type = 1, lu.user_phone, su.phone) AS headPhone, " +
+            "IF(cc.comment_type = 2, si.store_name, NULL) AS storeName, " +
             "IF(llr.dianzan_id IS NULL, '0', '1') AS isLike " +
             "FROM common_comment cc " +
             "LEFT JOIN life_user lu ON cc.user_id = lu.id AND lu.delete_flag = 0 " +
+            "LEFT JOIN store_info si ON cc.merchant_id = si.id AND si.delete_flag = 0 " +
             "LEFT JOIN store_user su ON cc.merchant_id = su.store_id AND su.delete_flag = 0 " +
             "AND su.id = (SELECT MIN(su2.id) FROM store_user su2 WHERE su2.store_id = cc.merchant_id AND su2.delete_flag = 0) " +
             "LEFT JOIN life_like_record llr ON llr.huifu_id = cc.id " +

+ 7 - 0
alien-entity/src/main/java/shop/alien/mapper/LifeUserPushDeviceMapper.java

@@ -0,0 +1,7 @@
+package shop.alien.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import shop.alien.entity.store.LifeUserPushDevice;
+
+public interface LifeUserPushDeviceMapper extends BaseMapper<LifeUserPushDevice> {
+}

+ 1 - 1
alien-lawyer/src/main/java/shop/alien/lawyer/service/impl/LawyerConsultationOrderServiceImpl.java

@@ -565,7 +565,7 @@ public class LawyerConsultationOrderServiceImpl extends ServiceImpl<LawyerConsul
 
         // 生成订单编号:LAW + 年月日(8位数字)+ 随机5位数字
         String orderNumber = generateOrderNumber();
-        order.setOrderNumber(orderNumber);
+        order.setOrderNumber(!StringUtils.isEmpty(lawyerConsultationOrder.getOrderNumber())?lawyerConsultationOrder.getOrderNumber():orderNumber);
         log.info("生成订单编号:orderNumber={}", orderNumber);
 
         // 计算本单收益(平台收益)

+ 47 - 0
alien-store/src/main/java/shop/alien/store/config/UniPushProperties.java

@@ -0,0 +1,47 @@
+package shop.alien.store.config;
+
+import lombok.Data;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.cloud.context.config.annotation.RefreshScope;
+import org.springframework.stereotype.Component;
+
+/**
+ * uniCloud 云函数 URL 化后,由 Java 侧发起推送的 HTTP 配置。
+ * 云函数内需使用 uni-cloud-push 的 sendMessage;请求体字段需与云函数约定一致。
+ */
+@Data
+@Component
+@RefreshScope
+@ConfigurationProperties(prefix = "alien.unipush")
+public class UniPushProperties {
+
+    /**
+     * 是否启用 HTTP 调用云函数推送(未配置 url 时 {@link shop.alien.store.service.UniPushCloudInvokeService} 会直接跳过)
+     */
+    private boolean enabled = false;
+
+    /**
+     * 云函数 URL 化完整地址,例如 https://xxx.com/push/send
+     */
+    private String cloudFunctionUrl = "";
+
+    /**
+     * 可选:鉴权请求头名,如 X-Api-Secret
+     */
+    private String authHeaderName = "";
+
+    /**
+     * 可选:鉴权请求头值
+     */
+    private String authHeaderValue = "";
+
+    /**
+     * 连接超时毫秒
+     */
+    private int connectTimeoutMs = 5000;
+
+    /**
+     * 读超时毫秒
+     */
+    private int readTimeoutMs = 10000;
+}

+ 62 - 0
alien-store/src/main/java/shop/alien/store/controller/LifeUserPushDeviceController.java

@@ -0,0 +1,62 @@
+package shop.alien.store.controller;
+
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiImplicitParam;
+import io.swagger.annotations.ApiImplicitParams;
+import io.swagger.annotations.ApiOperation;
+import io.swagger.annotations.ApiOperationSupport;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.web.bind.annotation.*;
+import shop.alien.entity.result.R;
+import shop.alien.entity.store.LifeUserPushDevice;
+import shop.alien.entity.store.UserLoginInfo;
+import shop.alien.store.dto.LifeUserPushBindDto;
+import shop.alien.store.service.LifeUserPushDeviceService;
+import shop.alien.util.common.TokenInfo;
+import springfox.documentation.annotations.ApiIgnore;
+
+import java.util.List;
+
+/**
+ * uni-push 等设备 cid 绑定;业务主键与类型取自登录态 JWT(userId + userType),不信任客户端传参区分身份。
+ * <p>userType:user → life_user.id;store/merchant → store_user.id;lawyer → lawyer_user.id</p>
+ */
+@Api(tags = {"用户推送设备"})
+@Slf4j
+@RestController
+@CrossOrigin
+@RequestMapping("/lifeUserPushDevice")
+@RequiredArgsConstructor
+public class LifeUserPushDeviceController {
+
+    private final LifeUserPushDeviceService lifeUserPushDeviceService;
+
+    @ApiOperation("绑定当前用户与 push cid(登录后调用)")
+    @ApiOperationSupport(order = 1)
+    @PostMapping("/bind")
+    public R<String> bind(@ApiIgnore @TokenInfo UserLoginInfo userLoginInfo,
+                          @RequestBody LifeUserPushBindDto dto) {
+        log.info("LifeUserPushDeviceController.bind, userId={}",
+                userLoginInfo == null ? null : userLoginInfo.getUserId());
+        return lifeUserPushDeviceService.bind(userLoginInfo, dto);
+    }
+
+    @ApiOperation("解绑指定 cid(仅删除当前用户名下记录)")
+    @ApiOperationSupport(order = 2)
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "pushClientId", value = "cid", required = true, paramType = "query", dataType = "String")
+    })
+    @PostMapping("/unbind")
+    public R<String> unbind(@ApiIgnore @TokenInfo UserLoginInfo userLoginInfo,
+                            @RequestParam String pushClientId) {
+        return lifeUserPushDeviceService.unbindByCid(userLoginInfo, pushClientId);
+    }
+
+    @ApiOperation("当前用户已绑定的推送设备列表")
+    @ApiOperationSupport(order = 3)
+    @GetMapping("/listMine")
+    public R<List<LifeUserPushDevice>> listMine(@ApiIgnore @TokenInfo UserLoginInfo userLoginInfo) {
+        return lifeUserPushDeviceService.listMine(userLoginInfo);
+    }
+}

+ 26 - 0
alien-store/src/main/java/shop/alien/store/dto/LifeUserPushBindDto.java

@@ -0,0 +1,26 @@
+package shop.alien.store.dto;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.io.Serializable;
+
+/**
+ * App 上报 uni-push cid 与当前登录用户绑定
+ */
+@Data
+@ApiModel(description = "绑定推送 cid 请求体")
+public class LifeUserPushBindDto implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    @ApiModelProperty(value = "uni.getPushClientId 返回的 cid", required = true)
+    private String pushClientId;
+
+    @ApiModelProperty(value = "平台:ios、android、harmony 等")
+    private String platform;
+
+    @ApiModelProperty(value = "DCloud 应用 appid,如 __UNI__xxxx")
+    private String dcloudAppId;
+}

+ 129 - 2
alien-store/src/main/java/shop/alien/store/service/LifeCommentService.java

@@ -8,6 +8,7 @@ import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.BeanUtils;
+import org.springframework.beans.factory.annotation.Value;
 import org.springframework.stereotype.Service;
 import org.springframework.util.CollectionUtils;
 import org.springframework.util.ObjectUtils;
@@ -36,6 +37,10 @@ import java.util.stream.Collectors;
 @RequiredArgsConstructor
 public class LifeCommentService {
 
+    /** App 点击推送后跳转页面(uni-app 页面路径) */
+    private static final String DYNAMICS_LIKE_PUSH_OPEN_PATH =
+            "pages/secondHandTransactions/pages/message/noticesAndMessage";
+
     private final LifeCommentMapper lifeCommentMapper;
 
     private final StoreCommentMapper storeCommentMapper;
@@ -62,6 +67,20 @@ public class LifeCommentService {
 
     private final CommonCommentMapper commonCommentMapper;
 
+    private final LifeUserPersonalizationSettingService lifeUserPersonalizationSettingService;
+
+    private final LifeUserPushDeviceService lifeUserPushDeviceService;
+
+    private final UniPushCloudInvokeService uniPushCloudInvokeService;
+
+    private final StoreUserMapper storeUserMapper;
+
+    /**
+     * 系统app通知开关
+     */
+    @Value("${alien.unipush.enabled:false}")
+    private boolean uniPushOn;
+
     /**
      * 点赞操作
      * <p>
@@ -121,9 +140,29 @@ public class LifeCommentService {
             // 根据类型更新对应表的点赞数
             int updateResult = updateLikeCountByType(huifuId, type);
             
-            // 如果是动态类型,发送通知
+            // 动态点赞:按「被点赞动态的发布者」的个性化设置决定是否通知(接收方是否愿意收点赞类通知)
             if (updateResult > 0 && CommonConstant.LIKE_TYPE_DYNAMICS.equals(type)) {
-                insertNotice(userId, huifuId, type);
+                Integer receiverLifeUserId = resolveLifeUserIdFromDynamicsAuthorPhoneId(huifuId);
+                boolean suppressNotice = receiverLifeUserId != null
+                        && lifeUserPersonalizationSettingService.shouldSuppressLikeRelatedNotice(receiverLifeUserId);
+                if (!suppressNotice) {
+                    try {
+                        insertNotice(userId, huifuId, type);
+                    } catch (Exception e) {
+                        log.error("动态点赞站内信保存失败, huifuId={}", huifuId, e);
+                    }
+
+                    // 发送系统通知开关
+                    if(uniPushOn){
+                        try {
+                            sendDynamicsLikeAppPushByDynamicsId(Integer.parseInt(huifuId.trim()));
+                        } catch (NumberFormatException e) {
+                            log.warn("动态点赞 App 推送跳过:huifuId 非合法动态 id, huifuId={}", huifuId);
+                        } catch (Exception e) {
+                            log.warn("动态点赞 App 推送失败, huifuId={}, err={}", huifuId, e.getMessage());
+                        }
+                    }
+                }
             }
             
             log.info("点赞操作完成,userId={},huifuId={},type={},更新结果={}", userId, huifuId, type, updateResult);
@@ -212,6 +251,37 @@ public class LifeCommentService {
     }
 
     /**
+     * 根据动态主键查发布者 phoneId,仅当为 C 端 user_ 前缀时解析为 life_user.id;门店动态 store_ 无 C 端个性化表则返回 null(不拦截通知)。
+     */
+    private Integer resolveLifeUserIdFromDynamicsAuthorPhoneId(String dynamicsIdStr) {
+        if (!StringUtils.hasText(dynamicsIdStr)) {
+            return null;
+        }
+        try {
+            int dynamicsId = Integer.parseInt(dynamicsIdStr.trim());
+            LifeUserDynamics dynamics = lifeUserDynamicsMapper.selectById(dynamicsId);
+            if (dynamics == null || !StringUtils.hasText(dynamics.getPhoneId())) {
+                return null;
+            }
+            String phoneId = dynamics.getPhoneId().trim();
+            if (!phoneId.startsWith("user_")) {
+                return null;
+            }
+            String phone = phoneId.substring("user_".length());
+            if (!StringUtils.hasText(phone)) {
+                return null;
+            }
+            LifeUser u = lifeUserMapper.selectOne(new LambdaQueryWrapper<LifeUser>()
+                    .eq(LifeUser::getUserPhone, phone)
+                    .eq(LifeUser::getDeleteFlag, 0)
+                    .last("LIMIT 1"));
+            return u != null ? u.getId() : null;
+        } catch (NumberFormatException e) {
+            return null;
+        }
+    }
+
+    /**
      * 发送通知
      */
     private void insertNotice(String userId, String huifuId, String type) {
@@ -229,6 +299,63 @@ public class LifeCommentService {
     }
 
     /**
+     * 仅发起 App 推送(uniCloud),与 {@link #insertNotice} 独立;未配置、无 cid 时静默返回。
+     */
+    private void sendDynamicsLikeAppPushByDynamicsId(int dynamicsId) {
+        LifeUserDynamics dynamics = lifeUserDynamicsMapper.selectById(dynamicsId);
+        sendDynamicsLikeAppPushOnly(dynamics, "动态通知", "点赞了你的动态", dynamicsId);
+    }
+
+    private void sendDynamicsLikeAppPushOnly(LifeUserDynamics dynamics, String title, String content, int dynamicsId) {
+        if (dynamics == null || !StringUtils.hasText(dynamics.getPhoneId())) {
+            return;
+        }
+        String phoneId = dynamics.getPhoneId().trim();
+        List<String> cids = resolvePushClientIdsForDynamicsAuthor(phoneId);
+        if (CollectionUtils.isEmpty(cids)) {
+            return;
+        }
+        Map<String, Object> payload = new HashMap<>(12);
+        payload.put("scene", "dynamics_like");
+        payload.put("dynamicsId", dynamicsId);
+        payload.put("noticeType", 0);
+        payload.put("path", DYNAMICS_LIKE_PUSH_OPEN_PATH);
+        uniPushCloudInvokeService.sendToClientIds(cids, title, content, payload);
+    }
+
+    private List<String> resolvePushClientIdsForDynamicsAuthor(String phoneId) {
+        if (phoneId.startsWith("user_")) {
+            String phone = phoneId.substring("user_".length());
+            if (!StringUtils.hasText(phone)) {
+                return Collections.emptyList();
+            }
+            LifeUser u = lifeUserMapper.selectOne(new LambdaQueryWrapper<LifeUser>()
+                    .eq(LifeUser::getUserPhone, phone)
+                    .eq(LifeUser::getDeleteFlag, 0)
+                    .last("LIMIT 1"));
+            if (u == null) {
+                return Collections.emptyList();
+            }
+            return lifeUserPushDeviceService.listPushClientIdsByUserId(u.getId(), PushDeviceOwnerType.USER.getCode());
+        }
+        if (phoneId.startsWith("store_")) {
+            String phone = phoneId.substring("store_".length());
+            if (!StringUtils.hasText(phone)) {
+                return Collections.emptyList();
+            }
+            StoreUser su = storeUserMapper.selectOne(new LambdaQueryWrapper<StoreUser>()
+                    .eq(StoreUser::getPhone, phone)
+                    .eq(StoreUser::getDeleteFlag, 0)
+                    .last("LIMIT 1"));
+            if (su == null) {
+                return Collections.emptyList();
+            }
+            return lifeUserPushDeviceService.listPushClientIdsByUserId(su.getId(), PushDeviceOwnerType.STORE.getCode());
+        }
+        return Collections.emptyList();
+    }
+
+    /**
      * 取消点赞操作
      * <p>
      * 检查是否已点赞,如果已点赞则删除点赞记录并更新对应表的点赞数

+ 46 - 0
alien-store/src/main/java/shop/alien/store/service/LifeUserPersonalizationSettingService.java

@@ -0,0 +1,46 @@
+package shop.alien.store.service;
+
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.service.IService;
+import shop.alien.entity.result.R;
+import shop.alien.entity.store.LifeUserPersonalizationSetting;
+
+public interface LifeUserPersonalizationSettingService extends IService<LifeUserPersonalizationSetting> {
+
+    R<String> add(LifeUserPersonalizationSetting setting);
+
+    R<String> deleteById(Integer id);
+
+    R<String> update(LifeUserPersonalizationSetting setting);
+
+    R<LifeUserPersonalizationSetting> getInfoById(Integer id);
+
+    R<LifeUserPersonalizationSetting> getByUserId(Integer userId);
+
+    /**
+     * 查询用户个性化设置:优先 Redis;未命中则与 {@link #getByUserId} 一致查库(含无记录时初始化)并写入 Redis。
+     *
+     * @param userId life_user.id
+     * @return 实体;userId 为空或查库/初始化失败时返回 null
+     */
+    LifeUserPersonalizationSetting getByUserIdCacheAside(Integer userId);
+
+    /**
+     * 是否不应向该用户发送与「收到点赞」相关的通知:notifyReceiveMessage=0,或 notifyReceiveMessage=1 且 notifyLike=0。
+     * 调用方应传入接收通知的一方对应的 life_user.id(例如动态点赞场景为动态发布者,而非点赞操作人)。
+     *
+     * @param userId life_user.id(接收方)
+     * @return userId 为空时 false(不拦截);有设置且满足上述条件时 true
+     */
+    boolean shouldSuppressLikeRelatedNotice(Integer userId);
+
+    /**
+     * 是否不应发送与关注相关的通知:notifyReceiveMessage=0,或 notifyReceiveMessage=1 且 notifyFollow=0。
+     *
+     * @param userId life_user.id(被关注方)
+     * @return userId 为空时 false(不拦截);有设置且满足上述条件时 true
+     */
+    boolean shouldSuppressFollowRelatedNotice(Integer userId);
+
+    R<IPage<LifeUserPersonalizationSetting>> list(Integer pageNum, Integer pageSize, Integer userId);
+}

+ 34 - 0
alien-store/src/main/java/shop/alien/store/service/LifeUserPushDeviceService.java

@@ -0,0 +1,34 @@
+package shop.alien.store.service;
+
+import shop.alien.entity.result.R;
+import shop.alien.entity.store.LifeUserPushDevice;
+import shop.alien.entity.store.UserLoginInfo;
+import shop.alien.store.dto.LifeUserPushBindDto;
+
+import java.util.List;
+
+public interface LifeUserPushDeviceService {
+
+    /**
+     * 将 cid 绑定到当前登录用户;同一 cid 全局唯一,重复上报会更新为当前用户并刷新平台等信息。
+     */
+    R<String> bind(UserLoginInfo login, LifeUserPushBindDto dto);
+
+    /**
+     * 当前用户解绑指定 cid(仅可删除本人绑定记录)
+     */
+    R<String> unbindByCid(UserLoginInfo login, String pushClientId);
+
+    /**
+     * 查询当前用户已绑定的设备列表
+     */
+    R<List<LifeUserPushDevice>> listMine(UserLoginInfo login);
+
+    /**
+     * 供业务侧按归属类型 + 业务用户主键查询 cid 列表(去重),用于组装云函数推送目标。
+     *
+     * @param userId    ownerType=user 时为 life_user.id;store 为 store_user.id;lawyer 为 lawyer_user.id
+     * @param ownerType {@link shop.alien.entity.store.PushDeviceOwnerType#getCode()}:user / store / lawyer
+     */
+    List<String> listPushClientIdsByUserId(int userId, String ownerType);
+}

+ 106 - 1
alien-store/src/main/java/shop/alien/store/service/LifeUserService.java

@@ -9,6 +9,7 @@ import com.baomidou.mybatisplus.core.toolkit.support.SFunction;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
 import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.commons.lang3.tuple.Triple;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -24,6 +25,8 @@ import shop.alien.entity.second.SecondRiskControlRecord;
 import shop.alien.entity.store.LifeFans;
 import shop.alien.entity.store.LifeNotice;
 import shop.alien.entity.store.LifeUser;
+import shop.alien.entity.store.PushDeviceOwnerType;
+import shop.alien.entity.store.StoreUser;
 import shop.alien.entity.store.vo.LifeMessageVo;
 import shop.alien.entity.store.vo.LifeUserVo;
 import shop.alien.entity.store.vo.WebSocketVo;
@@ -31,6 +34,7 @@ import shop.alien.mapper.LifeFansMapper;
 import shop.alien.mapper.LifeMessageMapper;
 import shop.alien.mapper.LifeNoticeMapper;
 import shop.alien.mapper.LifeUserMapper;
+import shop.alien.mapper.StoreUserMapper;
 import shop.alien.mapper.second.LifeUserLogMapper;
 import shop.alien.mapper.second.SecondRiskControlRecordMapper;
 import shop.alien.mapper.second.SecondUserCreditMapper;
@@ -48,10 +52,15 @@ import java.util.stream.Collectors;
 /**
  * 用户
  */
+@Slf4j
 @Service
 @RequiredArgsConstructor
 public class LifeUserService extends ServiceImpl<LifeUserMapper, LifeUser> {
 
+    /** App 点击推送后跳转页面(与动态点赞通知一致) */
+    private static final String FOLLOW_APP_PUSH_OPEN_PATH =
+            "pages/secondHandTransactions/pages/message/noticesAndMessage";
+
     private final LifeUserMapper lifeUserMapper;
 
     private final LifeFansMapper lifeFansMapper;
@@ -74,12 +83,26 @@ public class LifeUserService extends ServiceImpl<LifeUserMapper, LifeUser> {
 
     private final SecondRiskControlRecordMapper secondRiskControlRecordMapper;
 
+    private final LifeUserPersonalizationSettingService lifeUserPersonalizationSettingService;
+
+    private final LifeUserPushDeviceService lifeUserPushDeviceService;
+
+    private final UniPushCloudInvokeService uniPushCloudInvokeService;
+
+    private final StoreUserMapper storeUserMapper;
+
     @Autowired
     private RiskControlProperties riskControlProperties;
 
     @Value("${jwt.expiration-time}")
     private String effectiveTime;
 
+    /**
+     * 系统app通知开关
+     */
+    @Value("${alien.unipush.enabled:false}")
+    private boolean uniPushOn;
+
     public IPage<LifeUser> getStoresPage(int page, int size, String realName, String userPhone) {
         IPage<LifeUser> storePage = new Page<>(page, size);
         QueryWrapper<LifeUser> queryWrapper = new QueryWrapper<>();
@@ -126,12 +149,94 @@ public class LifeUserService extends ServiceImpl<LifeUserMapper, LifeUser> {
             if (!CollectionUtils.isEmpty(userList)) {
                 notice.setBusinessId(userList.get(0).getId());
             }
-            lifeNoticeMapper.insert(notice);
+            Integer followedLifeUserId = resolveLifeUserIdFromFollowedId(fans.getFollowedId());
+            boolean suppressNotice = followedLifeUserId != null
+                    && lifeUserPersonalizationSettingService.shouldSuppressFollowRelatedNotice(followedLifeUserId);
+            if (!suppressNotice) {
+                lifeNoticeMapper.insert(notice);
+
+                // 发送系统通知开关
+                if(uniPushOn) {
+                    try {
+                        sendFollowRelationAppPush(fans.getFollowedId());
+                    } catch (Exception e) {
+                        log.warn("关注 App 推送失败,followedId={},err={}", fans.getFollowedId(), e.getMessage());
+                    }
+                }
+            }
         }
 
         return num;
     }
 
+    /**
+     * 关注站内通知写入成功后,向被关注方已绑定 cid 的设备发 App 系统消息;与站内信条件一致,失败不影响关注与通知入库。
+     */
+    private void sendFollowRelationAppPush(String followedId) {
+        if (StringUtils.isBlank(followedId)) {
+            return;
+        }
+        List<String> cids = resolvePushClientIdsForFollowReceiver(followedId.trim());
+        if (CollectionUtils.isEmpty(cids)) {
+            return;
+        }
+        Map<String, Object> payload = new HashMap<>(8);
+        payload.put("scene", "follow");
+        payload.put("noticeType", 0);
+        payload.put("path", FOLLOW_APP_PUSH_OPEN_PATH);
+        uniPushCloudInvokeService.sendToClientIds(cids, "关注通知", "关注了你", payload);
+    }
+
+    private List<String> resolvePushClientIdsForFollowReceiver(String phoneId) {
+        if (phoneId.startsWith("user_")) {
+            String phone = phoneId.substring("user_".length());
+            if (StringUtils.isBlank(phone)) {
+                return Collections.emptyList();
+            }
+            LifeUser u = lifeUserMapper.selectOne(new LambdaQueryWrapper<LifeUser>()
+                    .eq(LifeUser::getUserPhone, phone)
+                    .eq(LifeUser::getDeleteFlag, 0)
+                    .last("LIMIT 1"));
+            if (u == null) {
+                return Collections.emptyList();
+            }
+            return lifeUserPushDeviceService.listPushClientIdsByUserId(u.getId(), PushDeviceOwnerType.USER.getCode());
+        }
+        if (phoneId.startsWith("store_")) {
+            String phone = phoneId.substring("store_".length());
+            if (StringUtils.isBlank(phone)) {
+                return Collections.emptyList();
+            }
+            StoreUser su = storeUserMapper.selectOne(new LambdaQueryWrapper<StoreUser>()
+                    .eq(StoreUser::getPhone, phone)
+                    .eq(StoreUser::getDeleteFlag, 0)
+                    .last("LIMIT 1"));
+            if (su == null) {
+                return Collections.emptyList();
+            }
+            return lifeUserPushDeviceService.listPushClientIdsByUserId(su.getId(), PushDeviceOwnerType.STORE.getCode());
+        }
+        return Collections.emptyList();
+    }
+
+    /**
+     * followedId 为 user_手机号 时解析为 life_user.id;店铺等非用户被关注方返回 null(不读个性化表,照常发通知)
+     */
+    private Integer resolveLifeUserIdFromFollowedId(String followedId) {
+        if (StringUtils.isBlank(followedId) || !followedId.startsWith("user_")) {
+            return null;
+        }
+        String phone = followedId.substring("user_".length());
+        if (StringUtils.isBlank(phone)) {
+            return null;
+        }
+        LifeUser u = lifeUserMapper.selectOne(new LambdaQueryWrapper<LifeUser>()
+                .eq(LifeUser::getUserPhone, phone)
+                .eq(LifeUser::getDeleteFlag, 0)
+                .last("LIMIT 1"));
+        return u != null ? u.getId() : null;
+    }
+
     public int cancelFans(LifeFans fans) {
         LambdaUpdateWrapper<LifeFans> wrapper = new LambdaUpdateWrapper<>();
         wrapper.eq(LifeFans::getFansId, fans.getFansId());

+ 82 - 0
alien-store/src/main/java/shop/alien/store/service/UniPushCloudInvokeService.java

@@ -0,0 +1,82 @@
+package shop.alien.store.service;
+
+import com.alibaba.fastjson2.JSONObject;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.http.HttpEntity;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.MediaType;
+import org.springframework.http.ResponseEntity;
+import org.springframework.http.client.SimpleClientHttpRequestFactory;
+import org.springframework.stereotype.Service;
+import org.springframework.web.client.RestTemplate;
+import shop.alien.store.config.UniPushProperties;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 调用 uniCloud URL 化云函数触发推送。请求体需与云函数内解析字段一致(示例见类注释)。
+ */
+@Slf4j
+@Service
+@RequiredArgsConstructor
+public class UniPushCloudInvokeService {
+
+    private final UniPushProperties properties;
+
+    /**
+     * 向云函数 POST JSON,默认结构(云函数内自行读取并调用 uniPush.sendMessage):
+     * <pre>
+     * {
+     *   "push_clientid": ["cid1","cid2"],
+     *   "title": "...",
+     *   "content": "...",
+     *   "payload": { }  // 可选,自定义透传
+     * }
+     * </pre>
+     */
+    public String sendToClientIds(List<String> pushClientIds, String title, String content, Map<String, Object> payload) {
+        if (!properties.isEnabled() || StringUtils.isBlank(properties.getCloudFunctionUrl())) {
+            log.debug("uni-push 云函数调用未启用或未配置 cloudFunctionUrl,跳过");
+            return null;
+        }
+        if (pushClientIds == null || pushClientIds.isEmpty()) {
+            log.warn("uni-push 推送跳过:pushClientIds 为空");
+            return null;
+        }
+
+        JSONObject body = new JSONObject();
+        body.put("push_clientid", pushClientIds);
+        body.put("title", title != null ? title : "");
+        body.put("content", content != null ? content : "");
+        if (payload != null && !payload.isEmpty()) {
+            body.put("payload", payload);
+        }
+
+        SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory();
+        factory.setConnectTimeout(properties.getConnectTimeoutMs());
+        factory.setReadTimeout(properties.getReadTimeoutMs());
+        RestTemplate restTemplate = new RestTemplate(factory);
+
+        HttpHeaders headers = new HttpHeaders();
+        headers.setContentType(MediaType.APPLICATION_JSON);
+        if (StringUtils.isNotBlank(properties.getAuthHeaderName())
+                && StringUtils.isNotBlank(properties.getAuthHeaderValue())) {
+            headers.set(properties.getAuthHeaderName(), properties.getAuthHeaderValue());
+        }
+
+        HttpEntity<String> entity = new HttpEntity<>(body.toJSONString(), headers);
+        try {
+            ResponseEntity<String> resp = restTemplate.postForEntity(
+                    properties.getCloudFunctionUrl(), entity, String.class);
+            String respBody = resp.getBody();
+            log.info("uni-push 云函数调用完成, status={}, body={}", resp.getStatusCode(), respBody);
+            return respBody;
+        } catch (Exception e) {
+            log.error("uni-push 云函数调用失败: {}", e.getMessage(), e);
+            throw e;
+        }
+    }
+}

+ 801 - 0
alien-store/src/main/java/shop/alien/store/service/impl/AlipayZftOnboardingServiceImpl.java

@@ -0,0 +1,801 @@
+package shop.alien.store.service.impl;
+
+import com.alibaba.fastjson.JSON;
+import com.alipay.api.*;
+import com.alipay.api.domain.*;
+import com.alipay.api.AlipayClient;
+import com.alipay.api.request.*;
+import com.alipay.api.response.*;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Service;
+import org.springframework.web.multipart.MultipartFile;
+import shop.alien.entity.result.R;
+import shop.alien.entity.store.AlipayZftCreateRecord;
+import shop.alien.entity.store.StorePaymentConfig;
+import shop.alien.entity.store.dto.AlipayZftBizRequestDto;
+import shop.alien.entity.store.dto.AlipayZftMerchantCreateDto;
+import shop.alien.entity.store.dto.AlipayZftMerchantSimplecreateDto;
+import shop.alien.mapper.AlipayZftCreateRecordMapper;
+import shop.alien.store.service.AlipayZftOnboardingService;
+import shop.alien.store.service.StorePaymentConfigService;
+
+import java.lang.reflect.Method;
+import java.util.Date;
+import java.nio.charset.StandardCharsets;
+import java.util.LinkedHashMap;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 使用门店 {@link StorePaymentConfig} 中的证书与密钥,以服务商/平台应用身份调用支付宝进件接口。
+ */
+@Slf4j
+@Service
+@RequiredArgsConstructor
+public class AlipayZftOnboardingServiceImpl implements AlipayZftOnboardingService {
+
+    @Value("${payment.aliPay.host}")
+    private String aliPayHost;
+
+    private final StorePaymentConfigService storePaymentConfigService;
+
+    private final AlipayZftCreateRecordMapper alipayZftCreateRecordMapper;
+
+    @Override
+    public R<String> zftConsult(AlipayZftBizRequestDto request) {
+        AntMerchantExpandIndirectZftConsultRequest api = new AntMerchantExpandIndirectZftConsultRequest();
+        return invoke(request, api);
+    }
+
+    @Override
+    public R<String> zftCreate(AntMerchantExpandIndirectZftCreateModel request1, Integer storeId) throws AlipayApiException {
+        // 初始化SDK
+        AlipayClient alipayClient = new DefaultAlipayClient(getAlipayConfig());
+
+        // 构造请求参数以调用接口
+        AntMerchantExpandIndirectZftCreateRequest request = new AntMerchantExpandIndirectZftCreateRequest();
+        AntMerchantExpandIndirectZftCreateModel model = new AntMerchantExpandIndirectZftCreateModel();
+
+        // 设置商户编号
+        model.setExternalId("2088560449225278");
+
+        // 设置商户类型
+        model.setMerchantType("01");
+
+        // 设置进件的二级商户名称
+        model.setName("爱丽恩(大连)餐饮有限公司");
+
+        // 设置商户别名
+        model.setAliasName("爱丽恩(大连)餐饮有限公司");
+
+        // 设置商户类别码 mcc
+        model.setMcc("B0002");
+
+        // 设置商户使用服务
+        List<String> service = new ArrayList<String>();
+        service.add("当面付");
+        service.add("app支付");
+        model.setService(service);
+
+        // 设置内景照
+        List<String> inDoorImages = new ArrayList<String>();
+        inDoorImages.add("889493da-4b52-4506-9a25-5dc29b96753b.png");
+        model.setInDoorImages(inDoorImages);
+
+        // 设置商户证件编号
+        model.setCertNo("91210202MAE7M17B6P");
+
+        // 设置商户证件类型
+        model.setCertType("201");
+
+        // 设置商户证件图片url
+        model.setCertImage("c2a7a12b-26c1-4eb3-bfcd-2f404fc2ca63.png");
+
+        // 设置证件反面图片
+//        model.setCertImageBack("25a10d04-ca9c-41aa-896f-3e0904bec470.jpg");
+
+        // 设置法人名称
+        model.setLegalName("唐永顺");
+
+        // 设置法人身份证号
+        model.setLegalCertNo("210106196804141555");
+
+        // 设置法人身份证正面url
+//        model.setLegalCertFrontImage("25a10d04-ca9c-41aa-896f-3e0904bec470.jpg");
+
+        // 设置法人身份证反面url
+//        model.setLegalCertBackImage("25a10d04-ca9c-41aa-896f-3e0904bec470.jpg");
+
+        // 设置经营地址
+        AddressInfo businessAddress = new AddressInfo();
+        businessAddress.setAddress("大连市中山区港湾街12A号8A-7号");
+        businessAddress.setDistrictCode("210202");
+//        businessAddress.setLatitude("60.270001");
+        businessAddress.setCityCode("210200");
+//        businessAddress.setPoiid("B0FFIVU189");
+        businessAddress.setProvinceCode("210000");
+//        businessAddress.setLongitude("120.760001");
+        model.setBusinessAddress(businessAddress);
+
+        // 设置客服电话
+//        model.setServicePhone("0571-85022088");
+
+        // 设置商户联系人信息
+        List<ContactInfo> contactInfos = new ArrayList<ContactInfo>();
+        ContactInfo contactInfos0 = new ContactInfo();
+//        contactInfos0.setIdCardNo("110000199001011234");
+//        contactInfos0.setPhone("0571-85022088");
+        contactInfos0.setName("王骏");
+        contactInfos0.setMobile("13352287427");
+//        contactInfos0.setEmail("user@domain.com");
+        contactInfos.add(contactInfos0);
+        model.setContactInfos(contactInfos);
+
+        // 设置结算银行卡
+//        List<SettleCardInfo> bizCards = new ArrayList<SettleCardInfo>();
+//        SettleCardInfo bizCards0 = new SettleCardInfo();
+//        bizCards0.setAccountInstName("招商银行");
+//        bizCards0.setBankCode("103290003044");
+//        bizCards0.setAccountType("DC");
+//        bizCards0.setUsageType("01");
+//        bizCards0.setAccountHolderName("张三");
+//        bizCards0.setAccountInstCity("杭州市");
+//        bizCards0.setAccountInstId("CMB");
+//        bizCards0.setAccountNo("6214855710610408");
+//        bizCards0.setAccountInstProvince("浙江省");
+//        bizCards0.setAccountBranchName("招商银行杭州高新支行");
+//        bizCards.add(bizCards0);
+//        model.setBizCards(bizCards);
+
+        // 设置商户行业资质
+//        List<IndustryQualificationInfo> qualifications = new ArrayList<IndustryQualificationInfo>();
+//        IndustryQualificationInfo qualifications0 = new IndustryQualificationInfo();
+//        qualifications0.setIndustryQualificationImage("c6c0c7a1-b9d5-4e5d-b9d4-9eed39f00e65.jpg");
+//        qualifications0.setIndustryQualificationType("323");
+//        qualifications.add(qualifications0);
+//        model.setQualifications(qualifications);
+
+        // 设置门头照
+        List<String> outDoorImages = new ArrayList<String>();
+        outDoorImages.add("64212801-093d-49a8-9769-5e2b288fb858.png");
+        model.setOutDoorImages(outDoorImages);
+
+        // 设置授权函
+//        model.setLicenseAuthLetterImage("c6c0c7a1-b9d5-4e5d-b9d4-9eed39f00e65.jpg");
+
+
+
+        // 设置二级商户与服务商的签约时间
+//        model.setSignTimeWithIsv("2015-04-15");
+
+        // 设置结算支付宝账号
+        model.setAlipayLogonId("ailien@alienyan.cn");
+
+        // 设置sites
+        List<SiteInfo> sites = new ArrayList<SiteInfo>();
+        SiteInfo sites0 = new SiteInfo();
+//        sites0.setIcpOrgName("支付宝(中国)网络技术有限公司");
+        sites0.setSiteType("02");
+//        sites0.setSiteDomain("www.alipay.com");
+//        sites0.setScreenshotImage("c6c0c7a1-b9d5-4e5d-b9d4-9eed39f00e65.jpg");
+//        sites0.setRemark("备注说明");
+//        sites0.setAuthLetterImage("c6c0c7a1-b9d5-4e5d-b9d4-9eed39f00e65.jpg");
+        sites0.setSiteName("应用2.0签约2025092468860692");
+//        sites0.setMarket("豌豆荚");
+//        sites0.setPassword("123456");
+//        sites0.setDownload("https://itunes.apple.com/cn/app/id333206289?mt=8");
+//        sites0.setTinyAppId("2021004105652035");
+//        sites0.setSiteUrl("https://open.alipay.com");
+//        sites0.setIcpServiceName("支付宝");
+//        sites0.setRemarkImage("c6c0c7a1-b9d5-4e5d-b9d4-9eed39f00e65.jpg");
+//        sites0.setAccount("测试账号");
+//        sites0.setIcpNo("沪ICP备15027489号-2");
+//        sites0.setStatus("ONLINE");
+        sites.add(sites0);
+        model.setSites(sites);
+
+        // 设置开票资料信息
+//        MerchantInvoiceInfo invoiceInfo = new MerchantInvoiceInfo();
+//        invoiceInfo.setMailTelephone("057162288888");
+//        invoiceInfo.setTaxPayerQualification("01");
+//        invoiceInfo.setAddress("浙江省杭州市西湖区西溪路蚂蚁金服");
+//        invoiceInfo.setAcceptElectronic(true);
+//        invoiceInfo.setTelephone("057162288888");
+//        invoiceInfo.setTitle("蚂蚁金服(杭州)信息技术有限公司");
+//        invoiceInfo.setMailName("张三");
+//        invoiceInfo.setAutoInvoice(true);
+//        invoiceInfo.setTaxPayerValid("19981011");
+//        invoiceInfo.setTaxNo("51010482542598631219");
+//        invoiceInfo.setBankName("中国银行");
+//        AddressInfo mailAddress = new AddressInfo();
+//        mailAddress.setAddress("万塘路18号黄龙时代广场B座");
+//        mailAddress.setDistrictCode("371002");
+//        mailAddress.setLatitude("60.270001");
+//        mailAddress.setCityCode("371000");
+//        mailAddress.setPoiid("B0FFIVU189");
+//        mailAddress.setProvinceCode("370000");
+//        mailAddress.setType("BUSINESS_ADDRESS");
+//        mailAddress.setLongitude("120.760001");
+//        invoiceInfo.setMailAddress(mailAddress);
+//        invoiceInfo.setBankAccount("1234567812345678123");
+//        model.setInvoiceInfo(invoiceInfo);
+
+        // 设置目前只有个体工商户商户类型要求填入本字段
+//        model.setCertName("xxxx小卖铺");
+
+        // 设置签约支付宝账户
+        model.setBindingAlipayLogonId("ailien@alienyan.cn");
+
+        // 设置默认可不填
+//        model.setLegalCertType("100");
+
+        // 设置默认结算规则
+//        DefaultSettleRule defaultSettleRule = new DefaultSettleRule();
+//        defaultSettleRule.setDefaultSettleType("alipayAccount");
+//        defaultSettleRule.setDefaultSettleTarget("myalipay@alipay.com");
+//        model.setDefaultSettleRule(defaultSettleRule);
+
+        // 设置商家性质
+//        model.setMerchantNature("01");
+
+        //java测试用
+//        request.setBizModel(model);
+        request.setBizModel(request1);
+        // 第三方代调用模式下请设置app_auth_token
+        // request.putOtherTextParam("app_auth_token", "<-- 请填写应用授权令牌 -->");
+
+        Date now = new Date();
+        AlipayZftCreateRecord record = new AlipayZftCreateRecord();
+        record.setStoreId(storeId);
+        record.setExternalId(request1 != null ? request1.getExternalId() : null);
+        record.setMerchantName(request1 != null ? request1.getName() : null);
+        record.setRequestJson(request1 != null ? JSON.toJSONString(request1) : "{}");
+        record.setCreatedTime(now);
+        record.setUpdatedTime(now);
+
+        AntMerchantExpandIndirectZftCreateResponse response;
+        try {
+            response = alipayClient.execute(request);
+        } catch (AlipayApiException e) {
+            record.setInvokeSuccess(false);
+            record.setSubMsg(StringUtils.isNotBlank(e.getErrMsg()) ? e.getErrMsg() : e.getMessage());
+            alipayZftCreateRecordMapper.insert(record);
+            throw e;
+        }
+        record.setResponseBody(response != null ? response.getBody() : null);
+        record.setInvokeSuccess(response != null && response.isSuccess());
+        if (response != null) {
+            record.setSubCode(response.getSubCode());
+            record.setSubMsg(response.getSubMsg());
+            record.setOrderId(request1 != null ? response.getOrderId() : null);
+        }
+        alipayZftCreateRecordMapper.insert(record);
+
+        if (response.isSuccess()) {
+            return R.data("调用成功", response.getBody());
+        } else {
+            return R.fail("调用失败:" + response.getBody());
+        }
+    }
+
+    private static AlipayConfig getAlipayConfig() {
+
+        String privateKey  = "MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCBTOvScNrk0mw+WbR6Qtx2+HrPxB5IU/V2HSPGZUTDNiCiSU9UZcuXlo0UNwMksFYR43btk4BHW9OsZETdr3uJzbEEko4jQPrY49V6V8d3yFUiexcbLKf9W2X+h7EwdPDahHk6wmM73Lm500xzv8JxdGeo/d2uGTh+qD66HcPW+QYj1YbjRV1JyCOiJ2OiwLcb28arNNLOgj6PktQ69FPSWS4hWrM7nl6DLRr6SdOlyfBT47gwPtW0BQ96/2b9O+qxKBPfrcqdRDH323HvrGFDwa8GoWTdchnKwFjmfIFHMvf+eq3tzilFi8H7vQRtzVRrndwrNa0z+1ss07fcWFDRAgMBAAECggEAItgU0OAizPk7vE22SiBMgy8RAX5rXrhpdIwDwQo3Tpf+kV1KKIdKJy6mFCWDDlcKysVOnlVag2BmmZVnzYnls8wfgQjxjuSK9Pno5JBVK51r+9/J6UPOfYMs6Duu700EPw7mEISj81TXJBGiD6tEfgiNisfm/mzDgbZbORKeXQbaTyrtB+GZn6FNSyyHA1vraARMrgfMEGNzQ4AbtfcUxGO+mejdTFs0PxAq6lovHBY3fYYHI1Nx6kf9iPoom/G4UrcMO67W6QU+1tOCZCXjy4bD2y421z/8XD73+WDyYp+Tjy0hTLqVZc7TpYAOximo1vMIUe23EdJJngdlkdpDFQKBgQDFyETL0knwBSakLfAe2BmFb2x++B4YXUnt4dGbCFBnVooxf5i06GVt/CrfkJhYK6hBSowOScIRf8P6BOSQptRZb2/I1ngQm4vcpAZw6EjUTlgOj/J3XJ+ApUNQnRqE28jDrE4m2RHg4BkQo6yA3DizJAluPCtFoCYDm1a7dV7u7wKBgQCnXEH5sD8VSxURv02/gn80g/uZIP/EOU3ycjBEqZdRGkNINwXT+zqrlZIGYb+bxLvU/R2OqKC5vhcyAL3T1A8ORYqPu5KLnAxg7C+rHuVilUWwCEH7POpCk+ETPXCZwcNvLNa5PIqBH/gdV9Y9PBTef6J4rN6V6TDFgosf5by8PwKBgDpVG71Fk1sAGep4RgbC05wgRc6Y3T9wXDqVzJ098YDY7D83E9HfbPLoWbjAS75Nef1vwCkCpgNFPIbD5KmpGp4aGM0SPC0hwzlbAy9PwxMi3CPHXsrHfZ+SnmzrOQQQUoErk40vnm9FiP74VwtWaD6llUZ25ohNeIi9yvHU5x/vAoGAdU2d1JOq85LHtsO+i9+8pyNnAsJ1YqTDtI5CtK2lqKvewswGIrlxOvi//AchVN3ExZmP0QDyfp31BhAs/T8iOl+Vqf7PzVjX+Eszch5aqwlzadmv3ZepnnamCGVE+hAsmkz0R6tebPjqYC7Ds/HbssQFLc4EyVBD5fwE5ZuR+OMCgYAvGHUYpi0dY9uMHXzL721tIopiwUfKCgLAn3yhSH3p7644NxHBqLLaBLVT2q7JAZQUaZUvXlwiyxU1zvo0xmvcbnB/Vd2qp8KbEUkvHyIYVJkM6Fn+9xBosorcrHv+7B2V1XR9WQcXvppxbN/8farWGuAA0anBD+UGrxd8B0/hHg==";
+        String alipayPublicKey = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAnxevClLpYc6c8A9lwmnftPO2BeU+X6aZ/+b/n1Cvq096VJKiHmqcsRgRbrnSlmPDHRrQpDti4en2Ys0L2lx7CObIr/2xP/jJVwjIO1iWHUj/w/NAbjv7dLW/FFY4SeNp8rU+hlgGgviyUxzonfNfr3v+o8grFqQq27/hiZJAofsQRMQu1dEQqoKdJj7eQLkTstiK5miJMyQ+Y3tLztrEUMBz/zRgaCEfGqmFmRZ2diy2X+1dGaX6H4+0QJ2u50eY2QTBkNuvREGbAn6/lttAgvg/+CywPYKGeC4xOfnl5wP8iA1QXYbXrVJRkZjU097nyOmSNhLy9KvJH2BNpojS1QIDAQAB";
+        AlipayConfig alipayConfig = new AlipayConfig();
+        alipayConfig.setServerUrl("https://openapi.alipay.com/gateway.do");
+        alipayConfig.setAppId("2021005196608960");
+        alipayConfig.setPrivateKey(privateKey);
+        alipayConfig.setFormat("json");
+        alipayConfig.setAlipayPublicKey(alipayPublicKey);
+        alipayConfig.setCharset("UTF-8");
+        alipayConfig.setSignType("RSA2");
+        return alipayConfig;
+    }
+
+
+
+    @Override
+    public R<String> zftSimplecreate(AlipayZftMerchantSimplecreateDto request) {
+        if (request == null) {
+            return R.fail("请求体不能为空");
+        }
+        if (request.getStoreId() == null) {
+            return R.fail("门店ID不能为空");
+        }
+        R<StorePaymentConfig> cfgCheck = loadConfigByStoreId(request.getStoreId());
+        if (R.isNotSuccess(cfgCheck) || cfgCheck.getData() == null) {
+            return R.fail(cfgCheck.getMsg());
+        }
+        try {
+            AntMerchantExpandIndirectZftSimplecreateRequest api = new AntMerchantExpandIndirectZftSimplecreateRequest();
+            api.setBizModel(toSimplecreateModel(request));
+            if (StringUtils.isNotBlank(request.getNotifyUrl())) {
+                api.setNotifyUrl(request.getNotifyUrl());
+            }
+            applyAlipayCommonParams(api, request.getAppAuthToken(), request.getApiVersion());
+            String appId = resolveAppId(cfgCheck.getData(), request.getAppId());
+            if (StringUtils.isBlank(appId)) {
+                return R.fail("app_id 不能为空,请传 app_id 或在门店支付配置中维护应用 ID");
+            }
+            AlipayClient client = new DefaultAlipayClient(buildAlipayConfig(cfgCheck.getData(), appId));
+            AntMerchantExpandIndirectZftSimplecreateResponse response = client.certificateExecute(api);
+            if (response == null) {
+                return R.fail("支付宝返回为空");
+            }
+            if (!response.isSuccess()) {
+                String sub = response.getSubMsg();
+                String msg = StringUtils.isNotBlank(sub) ? sub : "调用失败";
+                log.warn("支付宝 simplecreate 失败 subCode={} subMsg={}", response.getSubCode(), response.getSubMsg());
+                return R.fail(msg);
+            }
+            return R.data(response.getBody());
+        } catch (ReflectiveOperationException e) {
+            log.error("支付宝 simplecreate 公共参数设置失败", e);
+            return R.fail("构建请求失败:" + e.getMessage());
+        } catch (AlipayApiException e) {
+            log.error("支付宝 simplecreate 异常", e);
+            return R.fail(StringUtils.isNotBlank(e.getErrMsg()) ? e.getErrMsg() : e.getMessage());
+        }
+    }
+
+    @Override
+    public R<String> orderQuery(String orderId) {
+
+        if (StringUtils.isBlank(orderId)) {
+            return R.fail("orderId 不能为空");
+        }
+//        R<StorePaymentConfig> cfgCheck = loadConfigByStoreId(storeId);
+//        if (R.isNotSuccess(cfgCheck) || cfgCheck.getData() == null) {
+//            return R.fail(cfgCheck.getMsg());
+//        }
+        try {
+            // 初始化SDK
+            AlipayClient alipayClient = new DefaultAlipayClient(getAlipayConfig());
+            AntMerchantExpandIndirectZftorderQueryRequest request = new AntMerchantExpandIndirectZftorderQueryRequest();
+            AntMerchantExpandIndirectZftorderQueryModel model = new AntMerchantExpandIndirectZftorderQueryModel();
+            model.setOrderId(orderId.trim());
+            request.setBizModel(model);
+//            String appId = resolveAppId(cfgCheck.getData(), null);
+//            if (StringUtils.isBlank(appId)) {
+//                return R.fail("app_id 不能为空,请在门店支付配置中维护应用 ID");
+//            }
+            AntMerchantExpandIndirectZftorderQueryResponse response = alipayClient.execute(request);
+//            AntMerchantExpandIndirectZftorderQueryResponse response = client.certificateExecute(request);
+            if (response == null) {
+                return R.fail("支付宝返回为空");
+            }
+            if (!response.isSuccess()) {
+                String sub = response.getSubMsg();
+                String msg = StringUtils.isNotBlank(sub) ? sub : "调用失败";
+                log.warn("支付宝 order.query 失败 subCode={} subMsg={}", response.getSubCode(), response.getSubMsg());
+                return R.fail(msg);
+            }
+            return R.data(response.getBody());
+        } catch (AlipayApiException e) {
+            log.error("支付宝 order.query 异常 orderId={}", orderId, e);
+            return R.fail(StringUtils.isNotBlank(e.getErrMsg()) ? e.getErrMsg() : e.getMessage());
+        }
+    }
+
+    @Override
+    public R<String> zftModify(AlipayZftBizRequestDto request1) throws AlipayApiException {
+
+                AlipayClient alipayClient = new DefaultAlipayClient(getAlipayConfig());
+// 直付通二级商户作废接口:ant.merchant.expand.indirect.zft.delete
+        AntMerchantExpandIndirectZftDeleteRequest request = new AntMerchantExpandIndirectZftDeleteRequest();
+        request.setBizContent("{"
+                + "\"smid\":\"2088480335620690\"," // 二级商户号
+                + "\"external_id\":\"你的平台商户ID\"" // 可选,平台内部商户标识
+                + "}");
+        AntMerchantExpandIndirectZftDeleteResponse response = alipayClient.execute(request);
+        if (response.isSuccess()) {
+            return R.data("调用成功", response.getBody());
+        } else {
+            return R.data("调用失败", response.getBody());
+            // sdk版本是"4.38.0.ALL"及以上,可以参考下面的示例获取诊断链接
+            // String diagnosisUrl = DiagnosisUtils.getDiagnosisUrl(response);
+            // System.out.println(diagnosisUrl);
+        }
+    }
+
+    @Override
+    public R<String> supplementCreate(AlipayZftBizRequestDto request) {
+        AntMerchantExpandIndirectSupplementCreateRequest api = new AntMerchantExpandIndirectSupplementCreateRequest();
+        return invoke(request, api);
+    }
+
+    @Override
+    public R<String> imageUpload(Integer storeId, String imageType, MultipartFile imageContent,
+                                 String appId, String appAuthToken, String apiVersion) throws AlipayApiException {
+        // 初始化SDK
+        AlipayClient alipayClient = new DefaultAlipayClient(getAlipayConfig());
+//        if (storeId == null) {
+//            return R.fail("门店ID不能为空");
+//        }
+        if (StringUtils.isBlank(imageType)) {
+            return R.fail("imageType 不能为空");
+        }
+        if (imageContent == null || imageContent.isEmpty()) {
+            return R.fail("imageContent 不能为空");
+        }
+        if (imageContent.getSize() > 10L * 1024 * 1024) {
+            return R.fail("图片大小不能超过10MB");
+        }
+//        R<StorePaymentConfig> cfgCheck = loadConfigByStoreId(storeId);
+//        if (R.isNotSuccess(cfgCheck) || cfgCheck.getData() == null) {
+//            return R.fail(cfgCheck.getMsg());
+//        }
+        try {
+            AntMerchantExpandIndirectImageUploadRequest request = new AntMerchantExpandIndirectImageUploadRequest();
+            request.setImageType(imageType.trim().toLowerCase());
+            String fileName = StringUtils.defaultIfBlank(imageContent.getOriginalFilename(), "image_upload.bin");
+            request.setImageContent(new FileItem(fileName, imageContent.getBytes()));
+            applyAlipayCommonParams(request, appAuthToken, apiVersion);
+//            String resolvedAppId = resolveAppId(cfgCheck.getData(), appId);
+//            if (StringUtils.isBlank(resolvedAppId)) {
+//                return R.fail("app_id 不能为空,请传 app_id 或在门店支付配置中维护应用 ID");
+//            }
+//            AlipayClient client = new DefaultAlipayClient(buildAlipayConfig(cfgCheck.getData(), resolvedAppId));
+            AntMerchantExpandIndirectImageUploadResponse response = alipayClient.execute(request);
+            if (response == null) {
+                return R.fail("支付宝返回为空");
+            }
+            if (!response.isSuccess()) {
+                String sub = response.getSubMsg();
+                return R.fail(StringUtils.isNotBlank(sub) ? sub : "图片上传失败");
+            }
+            return R.data(response.getBody());
+        } catch (ReflectiveOperationException e) {
+            log.error("支付宝图片上传公共参数设置失败 storeId={}", storeId, e);
+            return R.fail("构建请求失败:" + e.getMessage());
+        } catch (Exception e) {
+            log.error("支付宝图片上传异常 storeId={}", storeId, e);
+            if (e instanceof AlipayApiException) {
+                AlipayApiException ex = (AlipayApiException) e;
+                return R.fail(StringUtils.isNotBlank(ex.getErrMsg()) ? ex.getErrMsg() : ex.getMessage());
+            }
+            return R.fail("图片上传失败:" + e.getMessage());
+        }
+    }
+
+    private <T extends AlipayResponse> R<String> invoke(AlipayZftBizRequestDto dto, AlipayRequest<T> apiRequest) {
+        if (dto == null) {
+            return R.fail("请求体不能为空");
+        }
+        R<StorePaymentConfig> cfgCheck = loadConfig(dto);
+        if (R.isNotSuccess(cfgCheck) || cfgCheck.getData() == null) {
+            return R.fail(cfgCheck.getMsg());
+        }
+        Map<String, Object> biz = dto.getBizContent();
+        String bizJson = (biz == null || biz.isEmpty()) ? "{}" : JSON.toJSONString(biz);
+        // 各 *Request 类实现 AlipayRequest 接口,自带 bizContent/notifyUrl 字段与 setter,并非继承 AlipayObject
+        try {
+            applyBizContentAndNotify(dto, apiRequest, bizJson);
+            applyAlipayCommonParams(apiRequest, dto.getAppAuthToken(), dto.getApiVersion());
+        } catch (ReflectiveOperationException e) {
+            log.error("设置支付宝请求参数失败", e);
+            return R.fail("构建请求失败:" + e.getMessage());
+        }
+        try {
+            String appId = resolveAppId(cfgCheck.getData(), dto.getAppId());
+            if (StringUtils.isBlank(appId)) {
+                return R.fail("app_id 不能为空,请传 app_id 或在门店支付配置中维护应用 ID");
+            }
+            AlipayClient client = new DefaultAlipayClient(buildAlipayConfig(cfgCheck.getData(), appId));
+            T response = client.certificateExecute(apiRequest);
+            if (response == null) {
+                return R.fail("支付宝返回为空");
+            }
+            if (!response.isSuccess()) {
+                String sub = response.getSubMsg();
+                String msg = StringUtils.isNotBlank(sub) ? sub : "调用失败";
+                log.warn("支付宝进件接口失败 method={} subCode={} subMsg={}",
+                        apiRequest.getApiMethodName(), response.getSubCode(), response.getSubMsg());
+                return R.fail(msg);
+            }
+            return R.data(response.getBody());
+        } catch (AlipayApiException e) {
+            log.error("支付宝进件接口异常 method={}", apiRequest.getApiMethodName(), e);
+            return R.fail(StringUtils.isNotBlank(e.getErrMsg()) ? e.getErrMsg() : e.getMessage());
+        }
+    }
+
+    private R<StorePaymentConfig> loadConfig(AlipayZftBizRequestDto dto) {
+        if (dto == null || dto.getStoreId() == null) {
+            return R.fail("门店ID不能为空");
+        }
+        return loadConfigByStoreId(dto.getStoreId());
+    }
+
+    private R<StorePaymentConfig> loadConfigByStoreId(Integer storeId) {
+        StorePaymentConfig config = storePaymentConfigService.getByStoreId(storeId);
+        if (config == null) {
+            return R.fail("该门店未配置支付参数");
+        }
+        if (StringUtils.isBlank(config.getAppSecretCert())
+                || config.getAppPublicCert() == null || config.getAppPublicCert().length == 0
+                || config.getAlipayPublicCert() == null || config.getAlipayPublicCert().length == 0
+                || config.getAlipayRootCert() == null || config.getAlipayRootCert().length == 0) {
+            return R.fail("门店支付配置不完整(缺少应用私钥或证书)");
+        }
+        return R.data(config);
+    }
+
+    /**
+     * @param appId 已解析出的应用 ID(请求覆盖或门店配置)
+     */
+    private com.alipay.api.AlipayConfig buildAlipayConfig(StorePaymentConfig config, String appId) {
+        com.alipay.api.AlipayConfig alipayConfig = new com.alipay.api.AlipayConfig();
+        alipayConfig.setServerUrl(aliPayHost);
+        alipayConfig.setAppId(appId);
+        alipayConfig.setPrivateKey(config.getAppSecretCert());
+        alipayConfig.setFormat("json");
+        alipayConfig.setCharset("UTF-8");
+        alipayConfig.setSignType("RSA2");
+        if (config.getAppPublicCert() != null && config.getAppPublicCert().length > 0) {
+            alipayConfig.setAppCertContent(new String(config.getAppPublicCert(), StandardCharsets.UTF_8));
+        }
+        if (config.getAlipayPublicCert() != null && config.getAlipayPublicCert().length > 0) {
+            alipayConfig.setAlipayPublicCertContent(new String(config.getAlipayPublicCert(), StandardCharsets.UTF_8));
+        }
+        if (config.getAlipayRootCert() != null && config.getAlipayRootCert().length > 0) {
+            alipayConfig.setRootCertContent(new String(config.getAlipayRootCert(), StandardCharsets.UTF_8));
+        }
+        return alipayConfig;
+    }
+
+    /**
+     * 请求体中的 app_id 优先,否则使用门店支付配置中的应用 ID。
+     */
+    private static String resolveAppId(StorePaymentConfig config, String overrideFromRequest) {
+        if (StringUtils.isNotBlank(overrideFromRequest)) {
+            return overrideFromRequest.trim();
+        }
+        if (config != null && StringUtils.isNotBlank(config.getAppId())) {
+            return config.getAppId().trim();
+        }
+        return null;
+    }
+
+    /**
+     * 支付宝 OpenAPI 的 Request 类均提供 {@code setBizContent(String)}、{@code setNotifyUrl(String)},
+     * 但声明在各自类上,接口 {@link AlipayRequest} 未包含这些方法,故用反射统一设置。
+     */
+    private void applyBizContentAndNotify(AlipayZftBizRequestDto dto, Object apiRequest, String bizJson)
+            throws ReflectiveOperationException {
+        Class<?> clazz = apiRequest.getClass();
+        Method setBiz = clazz.getMethod("setBizContent", String.class);
+        setBiz.invoke(apiRequest, bizJson);
+        if (StringUtils.isNotBlank(dto.getNotifyUrl())) {
+            Method setNotify = clazz.getMethod("setNotifyUrl", String.class);
+            setNotify.invoke(apiRequest, dto.getNotifyUrl());
+        }
+    }
+
+    /**
+     * 支付宝开放平台公共参数中需业务侧显式传入的部分:<br/>
+     * - {@code app_auth_token}:第三方代调用时通过 {@code putOtherTextParam} 追加;<br/>
+     * - {@code api_version}:若请求类支持 {@code setApiVersion} 则设置。<br/>
+     * 其余如 {@code app_id}、{@code method}、{@code format}、{@code charset}、{@code sign_type}、签名、{@code timestamp}、{@code version}
+     * 由 {@link DefaultAlipayClient} 与 {@link com.alipay.api.AlipayConfig} 自动处理。
+     */
+    private void applyAlipayCommonParams(Object apiRequest, String appAuthToken, String apiVersion)
+            throws ReflectiveOperationException {
+        if (apiRequest == null) {
+            return;
+        }
+        Class<?> clazz = apiRequest.getClass();
+        if (StringUtils.isNotBlank(appAuthToken)) {
+            Method put = clazz.getMethod("putOtherTextParam", String.class, String.class);
+            put.invoke(apiRequest, "app_auth_token", appAuthToken.trim());
+        }
+        if (StringUtils.isNotBlank(apiVersion)) {
+            try {
+                Method setVer = clazz.getMethod("setApiVersion", String.class);
+                setVer.invoke(apiRequest, apiVersion.trim());
+            } catch (NoSuchMethodException ignored) {
+                log.debug("当前请求类无 setApiVersion:{}", clazz.getName());
+            }
+        }
+    }
+
+    /**
+     * @return 错误文案;null 表示校验通过
+     */
+    private String validateZftCreateRequest(AlipayZftMerchantCreateDto dto) {
+        if (dto == null) {
+            return "请求体不能为空";
+        }
+        if (dto.getStoreId() == null) {
+            return "门店ID不能为空";
+        }
+        Map<String, Object> biz = buildZftCreateBizContent(dto);
+        return validateZftCreateBizMap(biz);
+    }
+
+    /**
+     * 校验合并后的 biz_content(字段可全部来自 biz_content,或来自顶层 + extraBiz)。
+     */
+    private static String validateZftCreateBizMap(Map<String, Object> biz) {
+        if (biz == null || biz.isEmpty()) {
+            return "biz_content 不能为空:请提供 biz_content,或提供 external_id、merchant_type、name、alias_name、mcc、service 等顶层字段";
+        }
+        String externalId = bizString(biz, "external_id");
+        if (StringUtils.isBlank(externalId)) {
+            return "external_id 不能为空";
+        }
+        if (externalId.length() > 64) {
+            return "external_id 长度不能超过64";
+        }
+        String merchantType = bizString(biz, "merchant_type");
+        if (StringUtils.isBlank(merchantType)) {
+            return "merchant_type 不能为空";
+        }
+        if (merchantType.length() > 20) {
+            return "merchant_type 长度不能超过20";
+        }
+        String name = bizString(biz, "name");
+        if (StringUtils.isBlank(name)) {
+            return "name 不能为空";
+        }
+        if (name.length() > 128) {
+            return "name 长度不能超过128";
+        }
+        String aliasName = bizString(biz, "alias_name");
+        if (StringUtils.isBlank(aliasName)) {
+            return "alias_name 不能为空";
+        }
+        if (aliasName.length() > 128) {
+            return "alias_name 长度不能超过128";
+        }
+        String mcc = bizString(biz, "mcc");
+        if (StringUtils.isBlank(mcc)) {
+            return "mcc 不能为空";
+        }
+        if (mcc.length() > 10) {
+            return "mcc 长度不能超过10";
+        }
+        if (!bizHasNonEmptyService(biz)) {
+            return "service 不能为空";
+        }
+        return null;
+    }
+
+    private static String bizString(Map<String, Object> biz, String key) {
+        Object v = biz == null ? null : biz.get(key);
+        if (v == null) {
+            return null;
+        }
+        if (v instanceof String) {
+            return ((String) v).trim();
+        }
+        return String.valueOf(v).trim();
+    }
+
+    private static boolean bizHasNonEmptyService(Map<String, Object> biz) {
+        Object v = biz.get("service");
+        if (v == null) {
+            return false;
+        }
+        if (v instanceof List) {
+            return !((List<?>) v).isEmpty();
+        }
+        if (v instanceof String) {
+            return StringUtils.isNotBlank((String) v);
+        }
+        if (v instanceof String[]) {
+            return ((String[]) v).length > 0;
+        }
+        return true;
+    }
+
+    /**
+     * 合并顺序:biz_content → extraBiz → 非空顶层字段(后者覆盖同名键)。
+     */
+    private Map<String, Object> buildZftCreateBizContent(AlipayZftMerchantCreateDto dto) {
+        Map<String, Object> biz = new LinkedHashMap<>();
+        if (dto.getBizContent() != null && !dto.getBizContent().isEmpty()) {
+            biz.putAll(dto.getBizContent());
+        }
+        if (dto.getExtraBiz() != null && !dto.getExtraBiz().isEmpty()) {
+            biz.putAll(dto.getExtraBiz());
+        }
+        if (StringUtils.isNotBlank(dto.getExternalId())) {
+            biz.put("external_id", dto.getExternalId().trim());
+        }
+        if (StringUtils.isNotBlank(dto.getMerchantType())) {
+            biz.put("merchant_type", dto.getMerchantType().trim());
+        }
+        if (StringUtils.isNotBlank(dto.getName())) {
+            biz.put("name", dto.getName().trim());
+        }
+        if (StringUtils.isNotBlank(dto.getAliasName())) {
+            biz.put("alias_name", dto.getAliasName().trim());
+        }
+        if (StringUtils.isNotBlank(dto.getMcc())) {
+            biz.put("mcc", dto.getMcc().trim());
+        }
+        if (dto.getService() != null && !dto.getService().isEmpty()) {
+            biz.put("service", dto.getService());
+        }
+        return biz;
+    }
+
+    private AntMerchantExpandIndirectZftSimplecreateModel toSimplecreateModel(AlipayZftMerchantSimplecreateDto d) {
+        AntMerchantExpandIndirectZftSimplecreateModel m = new AntMerchantExpandIndirectZftSimplecreateModel();
+        m.setExternalId(d.getExternalId());
+        m.setBindingAlipayLogonId(d.getBindingAlipayLogonId());
+        m.setAliasName(d.getAliasName());
+        m.setServicePhone(d.getServicePhone());
+        m.setName(d.getName());
+        m.setMcc(d.getMcc());
+        m.setService(d.getService());
+        m.setAlipayLogonId(d.getAlipayLogonId());
+        m.setOutDoorImages(d.getOutDoorImages());
+        m.setInDoorImages(d.getInDoorImages());
+        m.setLicenseAuthLetterImage(d.getLicenseAuthLetterImage());
+        if (d.getContactInfos() != null) {
+            m.setContactInfos(toContactInfo(d.getContactInfos()));
+        }
+        if (d.getDefaultSettleRule() != null) {
+            m.setDefaultSettleRule(toDefaultSettleRule(d.getDefaultSettleRule()));
+        }
+        if (d.getBizCards() != null) {
+            m.setBizCards(toSettleCardInfo(d.getBizCards()));
+        }
+        if (d.getBusinessAddress() != null) {
+            m.setBusinessAddress(toAddressInfo(d.getBusinessAddress()));
+        }
+        return m;
+    }
+
+    private static ContactInfo toContactInfo(AlipayZftMerchantSimplecreateDto.ContactPart p) {
+        ContactInfo c = new ContactInfo();
+        c.setName(p.getName());
+        c.setMobile(p.getMobile());
+        c.setIdCardNo(p.getIdCardNo());
+        c.setPhone(p.getPhone());
+        c.setEmail(p.getEmail());
+        return c;
+    }
+
+    private static DefaultSettleRule toDefaultSettleRule(AlipayZftMerchantSimplecreateDto.DefaultSettleRulePart p) {
+        DefaultSettleRule r = new DefaultSettleRule();
+        r.setDefaultSettleType(p.getDefaultSettleType());
+        r.setDefaultSettleTarget(p.getDefaultSettleTarget());
+        return r;
+    }
+
+    private static SettleCardInfo toSettleCardInfo(AlipayZftMerchantSimplecreateDto.SettleCardPart p) {
+        SettleCardInfo s = new SettleCardInfo();
+        s.setAccountInstName(p.getAccountInstName());
+        s.setBankCode(p.getBankCode());
+        s.setAccountType(p.getAccountType());
+        s.setUsageType(p.getUsageType());
+        s.setAccountHolderName(p.getAccountHolderName());
+        s.setAccountInstCity(p.getAccountInstCity());
+        s.setAccountInstId(p.getAccountInstId());
+        s.setAccountNo(p.getAccountNo());
+        s.setAccountBranchName(p.getAccountBranchName());
+        s.setAccountInstProvince(p.getAccountInstProvince());
+        return s;
+    }
+
+    private static AddressInfo toAddressInfo(AlipayZftMerchantSimplecreateDto.AddressPart p) {
+        AddressInfo a = new AddressInfo();
+        a.setAddress(p.getAddress());
+        a.setDistrictCode(p.getDistrictCode());
+        a.setLatitude(p.getLatitude());
+        a.setCityCode(p.getCityCode());
+        a.setPoiid(p.getPoiid());
+        a.setProvinceCode(p.getProvinceCode());
+        a.setLongitude(p.getLongitude());
+        a.setType(p.getType());
+        return a;
+    }
+}

+ 194 - 0
alien-store/src/main/java/shop/alien/store/service/impl/LifeUserPushDeviceServiceImpl.java

@@ -0,0 +1,194 @@
+package shop.alien.store.service.impl;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import shop.alien.entity.result.R;
+import shop.alien.entity.store.LawyerUser;
+import shop.alien.entity.store.LifeUser;
+import shop.alien.entity.store.LifeUserPushDevice;
+import shop.alien.entity.store.PushDeviceOwnerType;
+import shop.alien.entity.store.StoreUser;
+import shop.alien.entity.store.UserLoginInfo;
+import shop.alien.mapper.LawyerUserMapper;
+import shop.alien.mapper.LifeUserMapper;
+import shop.alien.mapper.LifeUserPushDeviceMapper;
+import shop.alien.mapper.StoreUserMapper;
+import shop.alien.store.dto.LifeUserPushBindDto;
+import shop.alien.store.service.LifeUserPushDeviceService;
+
+import java.util.Collections;
+import java.util.Date;
+import java.util.List;
+import java.util.stream.Collectors;
+
+@Slf4j
+@Service
+@RequiredArgsConstructor
+public class LifeUserPushDeviceServiceImpl implements LifeUserPushDeviceService {
+
+    private final LifeUserPushDeviceMapper lifeUserPushDeviceMapper;
+    private final LifeUserMapper lifeUserMapper;
+    private final StoreUserMapper storeUserMapper;
+    private final LawyerUserMapper lawyerUserMapper;
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public R<String> bind(UserLoginInfo login, LifeUserPushBindDto dto) {
+        if (login == null || login.getUserId() <= 0) {
+            return R.fail("请先登录");
+        }
+        PushDeviceOwnerType ownerType = PushDeviceOwnerType.fromJwtUserType(login.getType());
+        if (ownerType == null) {
+            return R.fail("当前登录类型不支持绑定推送设备");
+        }
+        if (dto == null || StringUtils.isBlank(dto.getPushClientId())) {
+            return R.fail("pushClientId 不能为空");
+        }
+        String cid = StringUtils.trim(dto.getPushClientId());
+        if (cid.length() > 128) {
+            return R.fail("pushClientId 过长");
+        }
+
+        R<String> valid = validateOwnerExists(ownerType, login.getUserId());
+        if (valid != null) {
+            return valid;
+        }
+
+        LifeUserPushDevice existing = lifeUserPushDeviceMapper.selectOne(
+                new LambdaQueryWrapper<LifeUserPushDevice>()
+                        .eq(LifeUserPushDevice::getPushClientId, cid)
+                        .last("LIMIT 1"));
+
+        Date now = new Date();
+        String platform = StringUtils.trimToNull(dto.getPlatform());
+        String appId = StringUtils.trimToNull(dto.getDcloudAppId());
+        if (platform != null && platform.length() > 32) {
+            platform = platform.substring(0, 32);
+        }
+        if (appId != null && appId.length() > 64) {
+            appId = appId.substring(0, 64);
+        }
+
+        String typeCode = ownerType.getCode();
+        if (existing != null) {
+            existing.setUserId(login.getUserId());
+            existing.setOwnerType(typeCode);
+            existing.setPlatform(platform);
+            existing.setDcloudAppId(appId);
+            existing.setUpdatedTime(now);
+            lifeUserPushDeviceMapper.updateById(existing);
+            log.info("更新推送设备绑定, ownerType={}, userId={}, cid={}", typeCode, login.getUserId(), cid);
+        } else {
+            LifeUserPushDevice row = new LifeUserPushDevice();
+            row.setUserId(login.getUserId());
+            row.setOwnerType(typeCode);
+            row.setPushClientId(cid);
+            row.setPlatform(platform);
+            row.setDcloudAppId(appId);
+            row.setCreatedTime(now);
+            row.setUpdatedTime(now);
+            lifeUserPushDeviceMapper.insert(row);
+            log.info("新增推送设备绑定, ownerType={}, userId={}, cid={}", typeCode, login.getUserId(), cid);
+        }
+        return R.success("绑定成功");
+    }
+
+    /**
+     * @return null 表示校验通过
+     */
+    private R<String> validateOwnerExists(PushDeviceOwnerType ownerType, int businessUserId) {
+        switch (ownerType) {
+            case USER:
+                LifeUser user = lifeUserMapper.selectById(businessUserId);
+                if (user == null) {
+                    return R.fail("用户不存在");
+                }
+                break;
+            case STORE:
+                StoreUser su = storeUserMapper.selectById(businessUserId);
+                if (su == null || (su.getDeleteFlag() != null && su.getDeleteFlag() != 0)) {
+                    return R.fail("门店用户不存在或已删除");
+                }
+                break;
+            case LAWYER:
+                LawyerUser lu = lawyerUserMapper.selectById(businessUserId);
+                if (lu == null || (lu.getDeleteFlag() != null && lu.getDeleteFlag() != 0)) {
+                    return R.fail("律师用户不存在或已删除");
+                }
+                break;
+            default:
+                return R.fail("不支持的归属类型");
+        }
+        return null;
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public R<String> unbindByCid(UserLoginInfo login, String pushClientId) {
+        if (login == null || login.getUserId() <= 0) {
+            return R.fail("请先登录");
+        }
+        PushDeviceOwnerType ownerType = PushDeviceOwnerType.fromJwtUserType(login.getType());
+        if (ownerType == null) {
+            return R.fail("当前登录类型不支持解绑推送设备");
+        }
+        if (StringUtils.isBlank(pushClientId)) {
+            return R.fail("pushClientId 不能为空");
+        }
+        String cid = StringUtils.trim(pushClientId);
+        int n = lifeUserPushDeviceMapper.delete(
+                new LambdaQueryWrapper<LifeUserPushDevice>()
+                        .eq(LifeUserPushDevice::getOwnerType, ownerType.getCode())
+                        .eq(LifeUserPushDevice::getUserId, login.getUserId())
+                        .eq(LifeUserPushDevice::getPushClientId, cid));
+        if (n <= 0) {
+            return R.fail("未找到绑定记录");
+        }
+        return R.success("解绑成功");
+    }
+
+    @Override
+    public R<List<LifeUserPushDevice>> listMine(UserLoginInfo login) {
+        if (login == null || login.getUserId() <= 0) {
+            return R.fail("请先登录");
+        }
+        PushDeviceOwnerType ownerType = PushDeviceOwnerType.fromJwtUserType(login.getType());
+        if (ownerType == null) {
+            return R.fail("当前登录类型不支持查询推送设备");
+        }
+        List<LifeUserPushDevice> list = lifeUserPushDeviceMapper.selectList(
+                new LambdaQueryWrapper<LifeUserPushDevice>()
+                        .eq(LifeUserPushDevice::getOwnerType, ownerType.getCode())
+                        .eq(LifeUserPushDevice::getUserId, login.getUserId())
+                        .orderByDesc(LifeUserPushDevice::getUpdatedTime));
+        return R.data(list);
+    }
+
+    @Override
+    public List<String> listPushClientIdsByUserId(int userId, String ownerType) {
+        if (userId <= 0) {
+            return Collections.emptyList();
+        }
+        PushDeviceOwnerType ot = PushDeviceOwnerType.fromCode(ownerType);
+        if (ot == null) {
+            return Collections.emptyList();
+        }
+        List<LifeUserPushDevice> list = lifeUserPushDeviceMapper.selectList(
+                new LambdaQueryWrapper<LifeUserPushDevice>()
+                        .eq(LifeUserPushDevice::getOwnerType, ot.getCode())
+                        .eq(LifeUserPushDevice::getUserId, userId)
+                        .select(LifeUserPushDevice::getPushClientId));
+        if (list == null || list.isEmpty()) {
+            return Collections.emptyList();
+        }
+        return list.stream()
+                .map(LifeUserPushDevice::getPushClientId)
+                .filter(StringUtils::isNotBlank)
+                .distinct()
+                .collect(Collectors.toList());
+    }
+}

+ 277 - 0
alien-store/src/main/java/shop/alien/store/strategy/payment/impl/AlipayPartnerPaymentStrategyImpl.java

@@ -0,0 +1,277 @@
+package shop.alien.store.strategy.payment.impl;
+
+import com.alipay.api.AlipayApiException;
+import com.alipay.api.AlipayClient;
+import com.alipay.api.AlipayConfig;
+import com.alipay.api.DefaultAlipayClient;
+import com.alipay.api.domain.*;
+import com.alipay.api.request.AlipayTradeAppPayRequest;
+import com.alipay.api.request.AlipayTradeQueryRequest;
+import com.alipay.api.response.AlipayTradeAppPayResponse;
+import com.alipay.api.response.AlipayTradeQueryResponse;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.collections4.map.HashedMap;
+import org.springframework.stereotype.Component;
+import shop.alien.entity.result.R;
+import shop.alien.mapper.AlipayZftCreateRecordMapper;
+import shop.alien.store.strategy.payment.PaymentStrategy;
+import shop.alien.util.common.constant.PaymentEnum;
+
+import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Random;
+
+@Slf4j
+@Component
+@RequiredArgsConstructor
+public class AlipayPartnerPaymentStrategyImpl implements PaymentStrategy {
+
+    private final AlipayZftCreateRecordMapper alipayZftCreateRecordMapper;
+
+
+
+    /**
+     * 生成支付宝直付通二级商户支付预订单
+     */
+    public R createAlipayZftPreOrder(String orderNo, String amount, String subject, String smid) throws AlipayApiException {
+        // 初始化SDK
+        AlipayClient alipayClient = new DefaultAlipayClient(getAlipayConfig());
+
+
+
+        // 构造请求参数以调用接口
+        AlipayTradeAppPayRequest request = new AlipayTradeAppPayRequest();
+        AlipayTradeAppPayModel model = new AlipayTradeAppPayModel();
+
+        // 设置商户订单号
+        // 生成随机订单号
+        orderNo = "alipay_zft_" + System.currentTimeMillis() + new Random().nextInt(999999);
+        model.setOutTradeNo(orderNo);
+
+        // 设置订单总金额
+        // 除以100
+        BigDecimal total = new BigDecimal(amount);
+        model.setTotalAmount(total.toPlainString());
+
+        // 设置订单标题
+        model.setSubject(subject);
+        // 1. 构建二级商户信息
+        SubMerchant subMerchant = new SubMerchant();
+        subMerchant.setMerchantId(smid); // 替换为实际的二级商户编号
+        // 【关键】设置二级商户信息(直付通必传)
+        model.setSubMerchant(subMerchant);
+
+        // 设置产品码
+//        model.setProductCode("QUICK_MSECURITY_PAY");
+
+        // 设置订单包含的商品列表信息
+//        List<GoodsDetail> goodsDetail = new ArrayList<GoodsDetail>();
+//        GoodsDetail goodsDetail0 = new GoodsDetail();
+//        goodsDetail0.setGoodsName("ipad");
+//        goodsDetail0.setAlipayGoodsId("20010001");
+//        goodsDetail0.setQuantity(1L);
+//        goodsDetail0.setPrice("2000");
+//        goodsDetail0.setGoodsId("apple-01");
+//        goodsDetail0.setGoodsCategory("34543238");
+//        goodsDetail0.setCategoriesTree("124868003|126232002|126252004");
+//        goodsDetail0.setShowUrl("http://www.alipay.com/xxx.jpg");
+//        goodsDetail.add(goodsDetail0);
+//        model.setGoodsDetail(goodsDetail);
+
+        // 设置订单绝对超时时间
+//        model.setTimeExpire("2016-12-31 10:05:00");
+
+        // 设置业务扩展参数
+//        ExtendParams extendParams = new ExtendParams();
+//        extendParams.setSysServiceProviderId("2088511833207846");
+//        extendParams.setHbFqSellerPercent("100");
+//        extendParams.setHbFqNum("3");
+//        extendParams.setIndustryRefluxInfo("{\"scene_code\":\"metro_tradeorder\",\"channel\":\"xxxx\",\"scene_data\":{\"asset_name\":\"ALIPAY\"}}");
+//        extendParams.setRoyaltyFreeze("true");
+//        extendParams.setCardType("S0JP0000");
+//        model.setExtendParams(extendParams);
+
+        // 设置公用回传参数
+//        model.setPassbackParams("merchantBizType%3d3C%26merchantBizNo%3d2016010101111");
+
+        // 设置商户的原始订单号
+//        model.setMerchantOrderNo("20161008001");
+
+        // 设置外部指定买家
+//        ExtUserInfo extUserInfo = new ExtUserInfo();
+//        extUserInfo.setCertType("IDENTITY_CARD");
+//        extUserInfo.setCertNo("362334768769238881");
+//        extUserInfo.setMobile("16587658765");
+//        extUserInfo.setName("李明");
+//        extUserInfo.setMinAge("18");
+//        extUserInfo.setNeedCheckInfo("F");
+//        extUserInfo.setIdentityHash("27bfcd1dee4f22c8fe8a2374af9b660419d1361b1c207e9b41a754a113f38fcc");
+//        model.setExtUserInfo(extUserInfo);
+
+        // 设置通知参数选项
+//        List<String> queryOptions = new ArrayList<String>();
+//        queryOptions.add("hyb_amount");
+//        queryOptions.add("enterprise_pay_info");
+//        model.setQueryOptions(queryOptions);
+        SettleInfo settleInfo = new SettleInfo();
+        SettleDetailInfo settleDetailInfo = new SettleDetailInfo();
+        settleDetailInfo.setTransInType("defaultSettle");
+        settleDetailInfo.setAmount(total.toString());
+        List<SettleDetailInfo>  settleDetailInfos = new ArrayList<>();
+        settleDetailInfos.add(settleDetailInfo);
+        settleInfo.setSettleDetailInfos(settleDetailInfos);
+        model.setSettleInfo(settleInfo);
+
+        request.setNotifyUrl("https://frp-off.com:40279/alienStore/alipayPartnerNotify");
+        request.setBizModel(model);
+
+        // 第三方代调用模式下请设置app_auth_token
+        // request.putOtherTextParam("app_auth_token", "<-- 请填写应用授权令牌 -->");
+//        model.setProductCode("QUICK_MSECURITY_PAY");
+        AlipayTradeAppPayResponse response = alipayClient.sdkExecute(request);
+        String orderStr = response.getBody();
+        System.out.println(orderStr);
+
+        if (response.isSuccess()) {
+            Map<String, Object> analyzeRequest = new HashedMap<>();
+            analyzeRequest.put("orderStr", orderStr);
+            analyzeRequest.put("orderNumber", orderNo);
+            return R.data(analyzeRequest);
+        } else {
+            return R.fail("调用失败");
+            // sdk版本是"4.38.0.ALL"及以上,可以参考下面的示例获取诊断链接
+            // String diagnosisUrl = DiagnosisUtils.getDiagnosisUrl(response);
+            // System.out.println(diagnosisUrl);
+        }
+    }
+
+    private static AlipayConfig getAlipayConfig() {
+        String privateKey  = "MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCBTOvScNrk0mw+WbR6Qtx2+HrPxB5IU/V2HSPGZUTDNiCiSU9UZcuXlo0UNwMksFYR43btk4BHW9OsZETdr3uJzbEEko4jQPrY49V6V8d3yFUiexcbLKf9W2X+h7EwdPDahHk6wmM73Lm500xzv8JxdGeo/d2uGTh+qD66HcPW+QYj1YbjRV1JyCOiJ2OiwLcb28arNNLOgj6PktQ69FPSWS4hWrM7nl6DLRr6SdOlyfBT47gwPtW0BQ96/2b9O+qxKBPfrcqdRDH323HvrGFDwa8GoWTdchnKwFjmfIFHMvf+eq3tzilFi8H7vQRtzVRrndwrNa0z+1ss07fcWFDRAgMBAAECggEAItgU0OAizPk7vE22SiBMgy8RAX5rXrhpdIwDwQo3Tpf+kV1KKIdKJy6mFCWDDlcKysVOnlVag2BmmZVnzYnls8wfgQjxjuSK9Pno5JBVK51r+9/J6UPOfYMs6Duu700EPw7mEISj81TXJBGiD6tEfgiNisfm/mzDgbZbORKeXQbaTyrtB+GZn6FNSyyHA1vraARMrgfMEGNzQ4AbtfcUxGO+mejdTFs0PxAq6lovHBY3fYYHI1Nx6kf9iPoom/G4UrcMO67W6QU+1tOCZCXjy4bD2y421z/8XD73+WDyYp+Tjy0hTLqVZc7TpYAOximo1vMIUe23EdJJngdlkdpDFQKBgQDFyETL0knwBSakLfAe2BmFb2x++B4YXUnt4dGbCFBnVooxf5i06GVt/CrfkJhYK6hBSowOScIRf8P6BOSQptRZb2/I1ngQm4vcpAZw6EjUTlgOj/J3XJ+ApUNQnRqE28jDrE4m2RHg4BkQo6yA3DizJAluPCtFoCYDm1a7dV7u7wKBgQCnXEH5sD8VSxURv02/gn80g/uZIP/EOU3ycjBEqZdRGkNINwXT+zqrlZIGYb+bxLvU/R2OqKC5vhcyAL3T1A8ORYqPu5KLnAxg7C+rHuVilUWwCEH7POpCk+ETPXCZwcNvLNa5PIqBH/gdV9Y9PBTef6J4rN6V6TDFgosf5by8PwKBgDpVG71Fk1sAGep4RgbC05wgRc6Y3T9wXDqVzJ098YDY7D83E9HfbPLoWbjAS75Nef1vwCkCpgNFPIbD5KmpGp4aGM0SPC0hwzlbAy9PwxMi3CPHXsrHfZ+SnmzrOQQQUoErk40vnm9FiP74VwtWaD6llUZ25ohNeIi9yvHU5x/vAoGAdU2d1JOq85LHtsO+i9+8pyNnAsJ1YqTDtI5CtK2lqKvewswGIrlxOvi//AchVN3ExZmP0QDyfp31BhAs/T8iOl+Vqf7PzVjX+Eszch5aqwlzadmv3ZepnnamCGVE+hAsmkz0R6tebPjqYC7Ds/HbssQFLc4EyVBD5fwE5ZuR+OMCgYAvGHUYpi0dY9uMHXzL721tIopiwUfKCgLAn3yhSH3p7644NxHBqLLaBLVT2q7JAZQUaZUvXlwiyxU1zvo0xmvcbnB/Vd2qp8KbEUkvHyIYVJkM6Fn+9xBosorcrHv+7B2V1XR9WQcXvppxbN/8farWGuAA0anBD+UGrxd8B0/hHg==";
+        String alipayPublicKey = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAnxevClLpYc6c8A9lwmnftPO2BeU+X6aZ/+b/n1Cvq096VJKiHmqcsRgRbrnSlmPDHRrQpDti4en2Ys0L2lx7CObIr/2xP/jJVwjIO1iWHUj/w/NAbjv7dLW/FFY4SeNp8rU+hlgGgviyUxzonfNfr3v+o8grFqQq27/hiZJAofsQRMQu1dEQqoKdJj7eQLkTstiK5miJMyQ+Y3tLztrEUMBz/zRgaCEfGqmFmRZ2diy2X+1dGaX6H4+0QJ2u50eY2QTBkNuvREGbAn6/lttAgvg/+CywPYKGeC4xOfnl5wP8iA1QXYbXrVJRkZjU097nyOmSNhLy9KvJH2BNpojS1QIDAQAB";
+        AlipayConfig alipayConfig = new AlipayConfig();
+        alipayConfig.setServerUrl("https://openapi.alipay.com/gateway.do");
+        alipayConfig.setAppId("2021005196608960");
+        alipayConfig.setPrivateKey(privateKey);
+        alipayConfig.setFormat("json");
+        alipayConfig.setAlipayPublicKey(alipayPublicKey);
+        alipayConfig.setCharset("UTF-8");
+        alipayConfig.setSignType("RSA2");
+        return alipayConfig;
+    }
+
+    @Override
+    public R createPreAliPayOrder(String orderNo, String price, String subject, String smid) throws Exception {
+        return createAlipayZftPreOrder(orderNo, price, subject, smid);
+    }
+
+    @Override
+    public R createPrePayOrder(String price, String subject) throws Exception {
+        return null;
+    }
+
+    @Override
+    public R handleNotify(String notifyData) throws Exception {
+        log.info("处理支付宝支付通知,通知数据:{}", notifyData);
+        // 通过订单ID查询记录中对应数据,取得storeId
+        // 通过storeId查询storeInfo对应数据,
+        //更新smid
+        return null;
+    }
+
+    @Override
+    public R searchOrderByOutTradeNoPath(String transactionId) throws Exception {
+        try {
+            AlipayClient alipayClient = new DefaultAlipayClient(getAlipayConfig());
+            AlipayTradeQueryRequest request = new AlipayTradeQueryRequest();
+            AlipayTradeQueryModel model = new AlipayTradeQueryModel();
+            model.setOutTradeNo(transactionId);
+            List<String> queryOptions = new ArrayList<>();
+            queryOptions.add("trade_settle_info");
+            model.setQueryOptions(queryOptions);
+            request.setBizModel(model);
+
+            AlipayTradeQueryResponse response = alipayClient.execute(request);
+            log.debug("支付宝订单查询 rawBody 长度={}", response.getBody() != null ? response.getBody().length() : 0);
+
+            Map<String, Object> data = buildAlipayTradeQueryData(response);
+            if (response.isSuccess()) {
+                return R.success("订单状态:" + data.get("tradeStatus"));
+            }
+            String sub = response.getSubMsg() != null ? response.getSubMsg() : "";
+            return R.fail("订单查询失败:" + response.getMsg() + "(" + sub + ")");
+        } catch (AlipayApiException e) {
+            log.error("支付宝订单查询异常, transactionId={}", transactionId, e);
+            return R.fail("查询异常:" + e.getMessage());
+        }
+    }
+
+    /**
+     * 将 {@link AlipayTradeQueryResponse} 转为与前端约定一致的 Map(与支付宝网关返回字段对齐,便于序列化为示例 JSON 结构)。
+     */
+    private static Map<String, Object> buildAlipayTradeQueryData(AlipayTradeQueryResponse response) {
+        Map<String, Object> data = new LinkedHashMap<>();
+        data.put("code", response.getCode());
+        data.put("msg", response.getMsg());
+        data.put("body", response.getBody());
+        if (response.getParams() != null && !response.getParams().isEmpty()) {
+            data.put("params", response.getParams());
+        }
+        data.put("buyerLogonId", response.getBuyerLogonId());
+        data.put("buyerPayAmount", response.getBuyerPayAmount());
+        data.put("buyerUserId", response.getBuyerUserId());
+
+        List<TradeFundBill> fundBillList = response.getFundBillList();
+        if (fundBillList != null && !fundBillList.isEmpty()) {
+            List<Map<String, String>> bills = new ArrayList<>();
+            for (TradeFundBill fb : fundBillList) {
+                if (fb == null) {
+                    continue;
+                }
+                Map<String, String> row = new LinkedHashMap<>();
+                row.put("amount", fb.getAmount());
+                row.put("fundChannel", fb.getFundChannel());
+                bills.add(row);
+            }
+            data.put("fundBillList", bills);
+        }
+
+        data.put("invoiceAmount", response.getInvoiceAmount());
+        data.put("outTradeNo", response.getOutTradeNo());
+        data.put("pointAmount", response.getPointAmount());
+        data.put("receiptAmount", response.getReceiptAmount());
+        data.put("sendPayDate", response.getSendPayDate());
+        data.put("totalAmount", response.getTotalAmount());
+        data.put("tradeNo", response.getTradeNo());
+
+        TradeSettleInfo tradeSettleInfo = response.getTradeSettleInfo();
+        if (tradeSettleInfo != null) {
+            Map<String, String> settle = new LinkedHashMap<>();
+            settle.put("tradeUnsettledAmount", tradeSettleInfo.getTradeUnsettledAmount());
+            data.put("tradeSettleInfo", settle);
+        }
+
+        data.put("tradeStatus", response.getTradeStatus());
+        data.put("errorCode", response.getCode());
+        data.put("success", response.isSuccess());
+        return data;
+    }
+
+    @Override
+    public String handleRefund(Map<String, String> params) throws Exception {
+        return "";
+    }
+
+    @Override
+    public R searchRefundRecordByOutRefundNo(String outRefundNo) throws Exception {
+        return null;
+    }
+
+    @Override
+    public String getType() {
+        return PaymentEnum.ALIPAY_PARTNER.getType();
+    }
+}