Эх сурвалжийг харах

完善微信支付分生产环境接入

- 激活微信支付 V3 配置,适配公钥模式(RSAPublicKeyConfig)
- 补齐修改金额/同步订单/退款 API(Strategy + Service + Controller)
- PayScoreSyncTask 增加 DOING 状态兜底轮询和 EXPIRED 处理
- 恢复扫码开门支付分校验,退款流程增加支付分分支
- 回调新增 PAYSCORE.REFUND_PAID 事件处理
- 清理审核截图相关入口及页面
- 新增 PayScoreState.EXPIRED 终态

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
skyline 2 долоо хоног өмнө
parent
commit
584bcaa975
22 өөрчлөгдсөн 948 нэмэгдсэн , 88 устгасан
  1. 4 0
      .gitignore
  2. 75 33
      haha-admin/src/main/java/com/haha/admin/task/PayScoreSyncTask.java
  3. 13 1
      haha-admin/src/main/resources/application.yml
  4. 7 2
      haha-common/src/main/java/com/haha/common/enums/PayScoreState.java
  5. 124 0
      haha-miniapp/src/main/java/com/haha/miniapp/controller/PayScoreController.java
  6. 15 14
      haha-miniapp/src/main/resources/application.yml
  7. 0 3
      haha-miniapp/src/main/resources/cert/apiclient_key.pem
  8. 2 0
      haha-mp/src/api/order.ts
  9. 40 0
      haha-mp/src/api/payscore.ts
  10. 24 0
      haha-mp/src/pages.json
  11. 2 2
      haha-mp/src/pages/index/index.vue
  12. 15 5
      haha-mp/src/pages/refund/refund.vue
  13. 1 2
      haha-service/src/main/java/com/haha/service/impl/DeviceServiceImpl.java
  14. 34 12
      haha-service/src/main/java/com/haha/service/payment/config/WxPayConfig.java
  15. 38 1
      haha-service/src/main/java/com/haha/service/payment/impl/PaymentServiceImpl.java
  16. 38 0
      haha-service/src/main/java/com/haha/service/payment/payscore/PayScoreModifyRequest.java
  17. 25 0
      haha-service/src/main/java/com/haha/service/payment/payscore/PayScoreRefundRequest.java
  18. 43 3
      haha-service/src/main/java/com/haha/service/payment/payscore/PayScoreService.java
  19. 20 0
      haha-service/src/main/java/com/haha/service/payment/payscore/PayScoreStrategy.java
  20. 20 0
      haha-service/src/main/java/com/haha/service/payment/payscore/PayScoreSyncRequest.java
  21. 176 10
      haha-service/src/main/java/com/haha/service/payment/payscore/impl/PayScoreServiceImpl.java
  22. 232 0
      haha-service/src/main/java/com/haha/service/payment/payscore/impl/WxPayScoreStrategy.java

+ 4 - 0
.gitignore

@@ -50,3 +50,7 @@ tsconfig.tsbuildinfo
 # 强制包含被全局 gitignore 排除的目录
 !haha-admin-web/src/views/inventory/logs/
 qdb-sdk/target/
+.agents/
+
+# 证书文件(含私钥,禁止提交)
+**/src/main/resources/cert/*.pem

+ 75 - 33
haha-admin/src/main/java/com/haha/admin/task/PayScoreSyncTask.java

@@ -4,6 +4,7 @@ import com.haha.common.enums.PayScoreState;
 import com.haha.common.utils.TraceIdUtils;
 import com.haha.entity.Order;
 import com.haha.service.OrderService;
+import com.haha.service.payment.payscore.PayScoreResult;
 import com.haha.service.payment.payscore.PayScoreService;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
@@ -37,7 +38,7 @@ public class PayScoreSyncTask {
     private static final int OVERDUE_HOURS = 24;
 
     /**
-     * 每 5 分钟执行一次,同步处于 USER_PAYING 状态的支付分订单
+     * 每 5 分钟执行一次,同步处于 USER_PAYING 和 DOING 状态的支付分订单
      */
     @Scheduled(cron = "0 */5 * * * ?")
     public void syncPayingOrders() {
@@ -53,50 +54,91 @@ public class PayScoreSyncTask {
                     .lt(Order::getServiceEndTime, LocalDateTime.now().minusMinutes(SYNC_DELAY_MINUTES))
                     .list();
 
-            if (payingOrders.isEmpty()) {
+            if (!payingOrders.isEmpty()) {
+                log.info("[支付分同步] 找到 {} 个 USER_PAYING 订单", payingOrders.size());
+                processSyncOrders(payingOrders);
+            } else {
                 log.info("[支付分同步] 无待同步的 USER_PAYING 订单");
-                return;
             }
 
-            log.info("[支付分同步] 找到 {} 个待同步订单", payingOrders.size());
+            // 同步 DOING 状态订单(兜底防止回调丢失)
+            syncDoingOrders();
 
-            int syncedCount = 0;
-            int stillPayingCount = 0;
+        } catch (Exception e) {
+            log.error("[支付分同步] 定时任务执行失败", e);
+        } finally {
+            TraceIdUtils.removeTraceId();
+        }
+    }
 
-            for (Order order : payingOrders) {
-                try {
-                    boolean synced = payScoreService.syncPayScoreStatus(order.getId());
-                    if (synced) {
-                        syncedCount++;
-                    }
+    /**
+     * 同步 DOING 状态订单(兜底防止回调丢失)
+     * 查询 payScoreState = DOING 且 serviceStartTime 超过 SYNC_DELAY_MINUTES 分钟的订单
+     */
+    private void syncDoingOrders() {
+        try {
+            List<Order> doingOrders = orderService.lambdaQuery()
+                    .eq(Order::getPayScoreState, PayScoreState.DOING.getCode())
+                    .lt(Order::getServiceStartTime, LocalDateTime.now().minusMinutes(SYNC_DELAY_MINUTES))
+                    .list();
 
-                    // 重新查询订单确认最新状态
-                    Order updatedOrder = orderService.getById(order.getId());
-                    if (updatedOrder != null && PayScoreState.isPaying(updatedOrder.getPayScoreState())) {
-                        stillPayingCount++;
+            if (doingOrders.isEmpty()) {
+                log.info("[支付分同步] 无待同步的 DOING 状态订单");
+                return;
+            }
 
-                        // 检查是否超过 24 小时
-                        if (updatedOrder.getServiceEndTime() != null) {
-                            long hoursElapsed = ChronoUnit.HOURS.between(
-                                    updatedOrder.getServiceEndTime(), LocalDateTime.now());
-                            if (hoursElapsed >= OVERDUE_HOURS) {
-                                log.warn("[支付分同步] 订单长时间未支付 - orderId: {}, 已过 {} 小时, failReason: {}",
-                                        updatedOrder.getId(), hoursElapsed, updatedOrder.getPayScoreFailReason());
-                            }
-                        }
+            log.info("[支付分同步] 找到 {} 个 DOING 状态订单,开始同步", doingOrders.size());
+            processSyncOrders(doingOrders);
+        } catch (Exception e) {
+            log.error("[支付分同步] 同步 DOING 状态订单失败", e);
+        }
+    }
+
+    /**
+     * 批量同步订单状态
+     */
+    private void processSyncOrders(List<Order> orders) {
+        int syncedCount = 0;
+        int stillPayingCount = 0;
+
+        for (Order order : orders) {
+            try {
+                PayScoreResult result = payScoreService.syncPayScoreStatus(order.getId());
+                if (result != null && result.isSuccess()) {
+                    syncedCount++;
+                }
+
+                // 重新查询订单确认最新状态
+                Order updatedOrder = orderService.getById(order.getId());
+                if (updatedOrder != null) {
+                    String state = updatedOrder.getPayScoreState();
+                    if (PayScoreState.isPaying(state)) {
+                        stillPayingCount++;
+                        checkOverdueWarning(updatedOrder);
+                    } else if (PayScoreState.isExpired(state)) {
+                        log.info("[支付分同步] 订单已过期 - orderId: {}", updatedOrder.getId());
                     }
-                } catch (Exception e) {
-                    log.error("[支付分同步] 同步订单状态异常 - orderId: {}", order.getId(), e);
                 }
+            } catch (Exception e) {
+                log.error("[支付分同步] 同步订单状态异常 - orderId: {}", order.getId(), e);
             }
+        }
 
-            log.info("[支付分同步] 同步完成 - 共处理 {} 单, 状态变更 {} 单, 仍待支付 {} 单",
-                    payingOrders.size(), syncedCount, stillPayingCount);
+        log.info("[支付分同步] 同步完成 - 共处理 {} 单, 状态变更 {} 单, 仍待支付 {} 单",
+                orders.size(), syncedCount, stillPayingCount);
+    }
 
-        } catch (Exception e) {
-            log.error("[支付分同步] 定时任务执行失败", e);
-        } finally {
-            TraceIdUtils.removeTraceId();
+    /**
+     * 检查订单逾期告警
+     */
+    private void checkOverdueWarning(Order order) {
+        if (order.getServiceEndTime() != null) {
+            long hoursElapsed = ChronoUnit.HOURS.between(
+                    order.getServiceEndTime(), LocalDateTime.now());
+            if (hoursElapsed >= OVERDUE_HOURS) {
+                log.warn("[支付分同步] 订单长时间未支付 - orderId: {}, 已过 {} 小时, failReason: {}",
+                        order.getId(), hoursElapsed, order.getPayScoreFailReason());
+            }
         }
     }
 }

+ 13 - 1
haha-admin/src/main/resources/application.yml

@@ -86,6 +86,18 @@ sa-token:
 
 # 微信小程序配置
 wechat:
+  # 支付配置 - 供支付分定时同步任务使用
+  pay:
+    app-id: wxef6ffc2591d04b1b # 小程序appid
+    mch-id: 1108941936 # 商户号
+    v3-api-key: 7P5nbvnQpfj6cXpWrywtYstvJk5frjdK
+    notify-url: https://api.fuyu.com/api/payment/callback/wechat
+    private-key-path: classpath:cert/apiclient_key.pem
+    merchant-serial-number: 58F965A745DF42AA4C8603EE6B2B663E1B100553
+    service-id: 00004000000000178152595581906374
+    pay-score-notify-url: https://api.fuyu.com/api/payment/callback/wechat_payscore
+    wechat-pay-public-key-id: PUB_KEY_ID_0111089419362026062400382393001400
+    wechat-pay-public-key-path: classpath:cert/pub_key.pem
   # 管理端小程序(haha-admin-mp)配置 - 用于补货员微信登录等
   admin-miniapp:
     app-id: 2601051549145878 # todo 要替换
@@ -97,7 +109,7 @@ haha:
     app-id: 2601051549145878
     app-secret: 06e1be59332b00de0baad82002cdbcb5
     base-url: http://api.hahabianli.com/
-  
+
   # 日志配置
   log:
     # 慢请求阈值(毫秒)

+ 7 - 2
haha-common/src/main/java/com/haha/common/enums/PayScoreState.java

@@ -9,7 +9,8 @@ public enum PayScoreState {
     DOING("DOING", "进行中"),
     USER_PAYING("USER_PAYING", "用户支付中"),
     DONE("DONE", "已完成"),
-    REVOKED("REVOKED", "已取消");
+    REVOKED("REVOKED", "已取消"),
+    EXPIRED("EXPIRED", "已过期");
 
     private final String code;
     private final String description;
@@ -32,7 +33,7 @@ public enum PayScoreState {
     }
 
     public static boolean isFinished(String code) {
-        return DONE.code.equals(code) || REVOKED.code.equals(code);
+        return DONE.code.equals(code) || REVOKED.code.equals(code) || EXPIRED.code.equals(code);
     }
 
     public static boolean isPaying(String code) {
@@ -46,4 +47,8 @@ public enum PayScoreState {
     public static boolean isRevoked(String code) {
         return REVOKED.code.equals(code);
     }
+
+    public static boolean isExpired(String code) {
+        return EXPIRED.code.equals(code);
+    }
 }

+ 124 - 0
haha-miniapp/src/main/java/com/haha/miniapp/controller/PayScoreController.java

@@ -6,12 +6,16 @@ import com.haha.common.constant.CallbackConstants;
 import com.haha.common.utils.RequestParseUtil;
 import com.haha.common.vo.Result;
 import com.haha.service.payment.payscore.PayScoreCallbackResult;
+import com.haha.service.payment.payscore.PayScoreModifyRequest;
+import com.haha.service.payment.payscore.PayScoreResult;
 import com.haha.service.payment.payscore.PayScoreService;
 import jakarta.servlet.http.HttpServletRequest;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.web.bind.annotation.*;
 
+import java.math.BigDecimal;
+import java.util.List;
 import java.util.Map;
 
 /**
@@ -207,6 +211,126 @@ public class PayScoreController {
         }
     }
 
+    /**
+     * 修改支付分订单金额(仅 USER_PAYING 状态可下调)
+     */
+    @PostMapping("/modify")
+    public Result<Map<String, Object>> modifyPayScoreOrder(@RequestBody Map<String, Object> params) {
+        try {
+            Long userId = StpUtil.getLoginIdAsLong();
+            log.info("用户 {} 请求修改支付分订单金额", userId);
+
+            if (!params.containsKey("orderId") || !params.containsKey("totalAmount")) {
+                return com.haha.common.constant.ResponseEnum.PARAM_FORMAT_ERROR.toResult("缺少必填参数");
+            }
+
+            Long orderId = Long.valueOf(params.get("orderId").toString());
+            BigDecimal totalAmount = new BigDecimal(params.get("totalAmount").toString());
+            String reason = params.containsKey("reason") ? params.get("reason").toString() : "金额调整";
+
+            List<PayScoreModifyRequest.PostPayment> payments = null;
+            if (params.containsKey("payments")) {
+                payments = com.alibaba.fastjson2.JSON.parseArray(
+                        params.get("payments").toString(), PayScoreModifyRequest.PostPayment.class);
+            }
+
+            PayScoreResult result = payScoreService.modifyPayScoreOrder(orderId, totalAmount, payments, reason);
+            if (result.isSuccess()) {
+                Map<String, Object> data = new java.util.HashMap<>();
+                data.put("outOrderNo", result.getOutOrderNo());
+                data.put("state", result.getState());
+                return Result.success("修改成功", data);
+            } else {
+                return Result.error(result.getErrorMsg());
+            }
+        } catch (Exception e) {
+            log.error("修改支付分订单金额失败", e);
+            return Result.error("修改失败:" + e.getMessage());
+        }
+    }
+
+    /**
+     * 同步订单支付信息(用户已通过其他方式支付时停止扣款)
+     */
+    @PostMapping("/sync-info/{orderId}")
+    public Result<Map<String, Object>> syncOrderPaidInfo(@PathVariable Long orderId,
+                                                          @RequestBody(required = false) Map<String, Object> params) {
+        try {
+            Long userId = StpUtil.getLoginIdAsLong();
+            log.info("用户 {} 请求同步支付分订单支付信息 - orderId: {}", userId, orderId);
+
+            String detail = params != null && params.containsKey("detail") ? params.get("detail").toString() : null;
+            PayScoreResult result = payScoreService.syncOrderPaidInfo(orderId, detail);
+            if (result.isSuccess()) {
+                Map<String, Object> data = new java.util.HashMap<>();
+                data.put("outOrderNo", result.getOutOrderNo());
+                data.put("state", result.getState());
+                return Result.success("同步成功", data);
+            } else {
+                return Result.error(result.getErrorMsg());
+            }
+        } catch (Exception e) {
+            log.error("同步支付分订单支付信息失败", e);
+            return Result.error("同步失败:" + e.getMessage());
+        }
+    }
+
+    /**
+     * 支付分退款
+     */
+    @PostMapping("/refund/{orderId}")
+    public Result<Map<String, Object>> refundPayScoreOrder(@PathVariable Long orderId,
+                                                            @RequestBody Map<String, Object> params) {
+        try {
+            Long userId = StpUtil.getLoginIdAsLong();
+            log.info("用户 {} 请求支付分退款 - orderId: {}", userId, orderId);
+
+            BigDecimal refundAmount = params.containsKey("refundAmount")
+                    ? new BigDecimal(params.get("refundAmount").toString())
+                    : null;
+            String reason = params.containsKey("reason") ? params.get("reason").toString() : "用户申请退款";
+
+            PayScoreResult result = payScoreService.refundPayScoreOrder(orderId, refundAmount, reason);
+            if (result.isSuccess()) {
+                Map<String, Object> data = new java.util.HashMap<>();
+                data.put("outOrderNo", result.getOutOrderNo());
+                data.put("refundId", result.getPackageStr());
+                return Result.success("退款申请成功", data);
+            } else {
+                return Result.error(result.getErrorMsg());
+            }
+        } catch (Exception e) {
+            log.error("支付分退款失败", e);
+            return Result.error("退款失败:" + e.getMessage());
+        }
+    }
+
+    /**
+     * 查询支付分退款状态
+     */
+    @GetMapping("/refund/{orderId}/{outRefundNo}")
+    public Result<Map<String, Object>> queryPayScoreRefund(@PathVariable Long orderId,
+                                                            @PathVariable String outRefundNo) {
+        try {
+            Long userId = StpUtil.getLoginIdAsLong();
+            log.info("用户 {} 查询支付分退款状态 - orderId: {}, refundNo: {}", userId, orderId, outRefundNo);
+
+            PayScoreResult result = payScoreService.queryPayScoreRefund(orderId, outRefundNo);
+            if (result.isSuccess()) {
+                Map<String, Object> data = new java.util.HashMap<>();
+                data.put("outOrderNo", result.getOutOrderNo());
+                data.put("status", result.getState());
+                data.put("amount", result.getTotalAmount());
+                return Result.success("查询成功", data);
+            } else {
+                return Result.error(result.getErrorMsg());
+            }
+        } catch (Exception e) {
+            log.error("查询退款状态失败", e);
+            return Result.error("查询失败:" + e.getMessage());
+        }
+    }
+
     /**
      * 支付分回调通知
      */

+ 15 - 14
haha-miniapp/src/main/resources/application.yml

@@ -60,19 +60,20 @@ server:
   servlet:
     context-path: /api
 
-# 微信相关配置
+# 微信小程序配置
 wechat:
-  # 支付配置 - 暂时屏蔽,等微信商户注册好后再配置
-  # pay:
-  #   app-id: wxef6ffc2591d04b1b
-  #   mch-id: 1888888888
-  #   mch-key: 88888888888888888888888888888888
-  #   v3-api-key: 88888888888888888888888888888888
-  #   notify-url: http://localhost:7077/api/payment/callback/wechat
-  #   private-key-path: classpath:cert/apiclient_key.pem
-  #   merchant-serial-number: YOUR_MERCHANT_SERIAL_NUMBER
-  #   service-id: YOUR_PAY_SCORE_SERVICE_ID
-  #   pay-score-notify-url: http://localhost:7077/api/payment/callback/wechat_payscore
+  # 支付配置 - 供支付分定时同步任务使用
+  pay:
+    app-id: wxef6ffc2591d04b1b # 小程序appid
+    mch-id: 1108941936 # 商户号
+    v3-api-key: 7P5nbvnQpfj6cXpWrywtYstvJk5frjdK
+    notify-url: https://api.fuyu.com/api/payment/callback/wechat
+    private-key-path: classpath:cert/apiclient_key.pem
+    merchant-serial-number: 58F965A745DF42AA4C8603EE6B2B663E1B100553
+    service-id: 00004000000000178152595581906374
+    pay-score-notify-url: https://api.fuyu.com/api/payment/callback/wechat_payscore
+    wechat-pay-public-key-id: PUB_KEY_ID_0111089419362026062400382393001400
+    wechat-pay-public-key-path: classpath:cert/pub_key.pem
   # 小程序配置
   miniapp:
     # 用户端小程序(haha-mp)
@@ -91,12 +92,12 @@ haha:
     app-id: 2601051549145878
     app-secret: 06e1be59332b00de0baad82002cdbcb5
     base-url: http://api.hahabianli.com/
-  
+
   # 公共配置
   common:
     # 图片域名前缀
     image-domain-prefix: https://img.hahabianli.com/
-  
+
   # 日志配置
   log:
     # 慢请求阈值(毫秒)

+ 0 - 3
haha-miniapp/src/main/resources/cert/apiclient_key.pem

@@ -1,3 +0,0 @@
------BEGIN PRIVATE KEY-----
-PLACEHOLDER_REPLACE_WITH_YOUR_REAL_MERCHANT_PRIVATE_KEY
------END PRIVATE KEY-----

+ 2 - 0
haha-mp/src/api/order.ts

@@ -39,6 +39,8 @@ export interface OrderInfo {
   paidAmount?: number;
   
   payStatus: string;
+  payChannel?: string; // 支付渠道:wechat/alipay/balance/wechat_payscore
+  payScoreOrderId?: string; // 微信支付分服务订单号
   status: number;
   statusText?: string;
   createTime: string;

+ 40 - 0
haha-mp/src/api/payscore.ts

@@ -66,3 +66,43 @@ export const createPayscoreOrder = (params: CreatePayscoreOrderRequest): Promise
 export const confirmEnablePayscore = (outOrderNo: string): Promise<{ enabled: boolean }> => {
   return post<{ enabled: boolean }>('/payscore/confirm-enable', { outOrderNo });
 };
+
+/**
+ * 修改支付分订单金额
+ */
+export interface ModifyPayscoreOrderRequest {
+  orderId: number;
+  totalAmount: number;
+  payments?: Array<{ name: string; amount: number; description?: string; count?: number }>;
+  reason?: string;
+}
+
+export const modifyPayscoreOrder = (params: ModifyPayscoreOrderRequest): Promise<{ outOrderNo: string; state: string }> => {
+  return post<{ outOrderNo: string; state: string }>('/payscore/modify', params);
+};
+
+/**
+ * 同步订单支付信息(用户已通过其他方式支付)
+ */
+export const syncPayscoreOrderInfo = (orderId: number, detail?: string): Promise<{ outOrderNo: string; state: string }> => {
+  return post<{ outOrderNo: string; state: string }>(`/payscore/sync-info/${orderId}`, { detail });
+};
+
+/**
+ * 支付分退款
+ */
+export interface RefundPayscoreOrderRequest {
+  refundAmount?: number;
+  reason?: string;
+}
+
+export const refundPayscoreOrder = (orderId: number, params: RefundPayscoreOrderRequest): Promise<{ outOrderNo: string; refundId: string }> => {
+  return post<{ outOrderNo: string; refundId: string }>(`/payscore/refund/${orderId}`, params);
+};
+
+/**
+ * 查询支付分退款状态
+ */
+export const queryPayscoreRefund = (orderId: number, outRefundNo: string): Promise<{ outOrderNo: string; status: string; amount: number }> => {
+  return get<{ outOrderNo: string; status: string; amount: number }>(`/payscore/refund/${orderId}/${outRefundNo}`);
+};

+ 24 - 0
haha-mp/src/pages.json

@@ -57,6 +57,30 @@
 				"navigationBarTextStyle": "black"
 			}
 		},
+		{
+			"path": "pages/payscore/success",
+			"style": {
+				"navigationBarTitleText": "开通成功",
+				"navigationBarBackgroundColor": "#07C160",
+				"navigationBarTextStyle": "white"
+			}
+		},
+		{
+			"path": "pages/payscore/scoreLow",
+			"style": {
+				"navigationBarTitleText": "支付分评估",
+				"navigationBarBackgroundColor": "#F57C00",
+				"navigationBarTextStyle": "white"
+			}
+		},
+		{
+			"path": "pages/payscore/settlement",
+			"style": {
+				"navigationBarTitleText": "结算完成",
+				"navigationBarBackgroundColor": "#07C160",
+				"navigationBarTextStyle": "white"
+			}
+		},
 		{
 			"path": "pages/orderDetail/orderDetail",
 			"style": {

+ 2 - 2
haha-mp/src/pages/index/index.vue

@@ -42,8 +42,8 @@
     <!-- 底部信息卡片 -->
     <view class="info-card">
       <view class="info-item">
-        <image class="info-icon payscore-icon" src="/static/icons/payscore.svg" mode="aspectFit"></image>
-        <text class="info-text">微信支付分 550分及以上优享</text>
+        <image class="info-icon payscore-icon" src="/static/icons/pay-socre-logo.png" mode="aspectFit"></image>
+        <text class="info-text">微信支付分 | 550分及以上优享</text>
       </view>
       <view class="info-item">
         <CustomerServiceButton

+ 15 - 5
haha-mp/src/pages/refund/refund.vue

@@ -144,6 +144,7 @@ import { getOrderDetail } from '../../api/order';
 import type { OrderInfo } from '../../api/order';
 import { post } from '../../utils/request';
 import { logger } from '../../utils/logger';
+import { refundPayscoreOrder } from '../../api/payscore';
 
 const orderId = ref<string>('');
 
@@ -350,16 +351,25 @@ const submitRefund = async () => {
   });
 
   try {
-    await post('/payment/refund', {
-      orderId: orderId.value,
-      reason: reasonText,
-      products: selectedProductIds
-    });
+    // 支付分订单走支付分退款接口
+    if (orderInfo.value.payChannel === 'wechat_payscore') {
+      await refundPayscoreOrder(Number(orderId.value), {
+        refundAmount: refundAmount.value,
+        reason: reasonText,
+      });
+    } else {
+      await post('/payment/refund', {
+        orderId: orderId.value,
+        reason: reasonText,
+        products: selectedProductIds,
+      });
+    }
 
     uni.showToast({ title: '退款申请成功', icon: 'success' });
     setTimeout(() => { uni.navigateBack(); }, 1500);
   } catch (error: any) {
     logger.error('退款申请失败:', error);
+    uni.showToast({ title: '退款失败,请重试', icon: 'none' });
   }
 };
 </script>

+ 1 - 2
haha-service/src/main/java/com/haha/service/impl/DeviceServiceImpl.java

@@ -257,9 +257,8 @@ public class DeviceServiceImpl extends ServiceImpl<DeviceMapper, Device> impleme
     public OpenDoorVO scanOpenDoor(String deviceId, Long userId) throws HahaException {
         log.info("用户 {} 请求打开设备 {}", userId, deviceId);
 
-        // TODO: 微信商户号开通后恢复此校验
         // 1. 检查用户支付分开通状态
-        // checkUserPayscoreStatus(userId);
+        checkUserPayscoreStatus(userId);
 
         // 2. 检查设备在线状态
         DeviceOnlineStatus onlineStatus = hahaClient.getDeviceApi().getOnlineStatus(deviceId);

+ 34 - 12
haha-service/src/main/java/com/haha/service/payment/config/WxPayConfig.java

@@ -1,7 +1,7 @@
 package com.haha.service.payment.config;
 
 import com.wechat.pay.java.core.Config;
-import com.wechat.pay.java.core.RSAAutoCertificateConfig;
+import com.wechat.pay.java.core.RSAPublicKeyConfig;
 import com.wechat.pay.java.core.notification.NotificationConfig;
 import com.wechat.pay.java.core.notification.NotificationParser;
 import com.wechat.pay.java.service.payments.jsapi.JsapiServiceExtension;
@@ -38,10 +38,7 @@ public class WxPayConfig {
     
     /** 商户号 */
     private String mchId;
-    
-    /** API V2 密钥(旧版,兼容保留) */
-    private String mchKey;
-    
+
     /** API V3 密钥 */
     private String v3ApiKey;
     
@@ -60,28 +57,37 @@ public class WxPayConfig {
     /** 支付分回调通知地址 */
     private String payScoreNotifyUrl;
 
+    /** 微信支付公钥ID(商户平台-API安全-微信支付公钥 处查看) */
+    private String wechatPayPublicKeyId;
+
+    /** 微信支付公钥文件路径(支持 classpath: 前缀) */
+    private String wechatPayPublicKeyPath;
+
     @Autowired
     private ResourceLoader resourceLoader;
     
     /**
      * 微信支付 SDK 核心配置
-     * 使用 RSAAutoCertificateConfig 自动管理平台证书
+     * 使用 RSAPublicKeyConfig(微信支付公钥模式)
      */
     @Bean
     public Config wxPaySdkConfig() {
-        log.info("初始化微信支付 V3 SDK 配置 - 商户号: {}, AppID: {}", mchId, appId);
-        
+        log.info("初始化微信支付 V3 SDK 配置(公钥模式)- 商户号: {}, AppID: {}", mchId, appId);
+
         try {
             String privateKeyContent = loadPrivateKey();
-            
-            Config config = new RSAAutoCertificateConfig.Builder()
+            String publicKeyContent = loadWechatPayPublicKey();
+
+            Config config = new RSAPublicKeyConfig.Builder()
                     .merchantId(mchId)
                     .privateKey(privateKeyContent)
                     .merchantSerialNumber(merchantSerialNumber)
                     .apiV3Key(v3ApiKey)
+                    .publicKeyId(wechatPayPublicKeyId)
+                    .publicKey(publicKeyContent)
                     .build();
-            
-            log.info("微信支付 V3 SDK 配置初始化成功");
+
+            log.info("微信支付 V3 SDK 配置初始化成功(公钥模式)");
             return config;
         } catch (Exception e) {
             log.error("微信支付 V3 SDK 配置初始化失败: {}", e.getMessage());
@@ -114,6 +120,22 @@ public class WxPayConfig {
         return new NotificationParser((NotificationConfig) wxPaySdkConfig);
     }
     
+    /**
+     * 读取微信支付公钥文件内容
+     */
+    private String loadWechatPayPublicKey() throws IOException {
+        if (wechatPayPublicKeyPath == null || wechatPayPublicKeyPath.isEmpty()) {
+            throw new IOException("微信支付公钥路径未配置");
+        }
+        Resource resource = resourceLoader.getResource(wechatPayPublicKeyPath);
+        if (!resource.exists()) {
+            throw new IOException("微信支付公钥文件不存在: " + wechatPayPublicKeyPath);
+        }
+        String content = new String(resource.getInputStream().readAllBytes(), StandardCharsets.UTF_8);
+        log.info("成功读取微信支付公钥文件: {}", wechatPayPublicKeyPath);
+        return content;
+    }
+
     /**
      * 读取商户私钥文件内容
      */

+ 38 - 1
haha-service/src/main/java/com/haha/service/payment/impl/PaymentServiceImpl.java

@@ -6,6 +6,8 @@ import com.haha.entity.Order;
 import com.haha.service.AccountService;
 import com.haha.service.OrderService;
 import com.haha.service.payment.*;
+import com.haha.service.payment.payscore.PayScoreResult;
+import com.haha.service.payment.payscore.PayScoreService;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
@@ -34,6 +36,9 @@ public class PaymentServiceImpl implements PaymentService {
 
     @Autowired
     private AccountService accountService;
+
+    @Autowired(required = false)
+    private PayScoreService payScoreService;
     
     @Override
     public PaymentResult createPayment(Long orderId, PaymentChannel channel, Map<String, String> extra) {
@@ -177,7 +182,39 @@ public class PaymentServiceImpl implements PaymentService {
         // 5. 根据订单的 payChannel 获取支付渠道
         PaymentChannel channel = PaymentChannel.fromCode(order.getPayChannel());
         
-        // 6. 如果 channel 为 null 或 BALANCE,直接更新退款状态(余额退款不经过第三方)
+        // 6. 如果 channel 为 WECHAT_PAYSCORE,走支付分退款流程
+        if (channel == PaymentChannel.WECHAT_PAYSCORE) {
+            if (payScoreService == null) {
+                log.error("退款失败: 支付分服务未初始化");
+                return RefundResult.builder()
+                        .success(false)
+                        .errorCode("SERVICE_UNAVAILABLE")
+                        .errorMsg("支付分服务未配置")
+                        .build();
+            }
+
+            BigDecimal actualRefundAmount = order.getPaidAmount() != null ? order.getPaidAmount() : order.getTotalAmount();
+            PayScoreResult payScoreResult = payScoreService.refundPayScoreOrder(
+                    orderId, actualRefundAmount, reason != null ? reason : "用户申请退款");
+
+            if (payScoreResult.isSuccess()) {
+                log.info("支付分退款成功: orderId={}, refundId={}", orderId, payScoreResult.getPackageStr());
+                return RefundResult.builder()
+                        .success(true)
+                        .refundId(payScoreResult.getPackageStr())
+                        .build();
+            } else {
+                log.warn("支付分退款失败: orderId={}, errorCode={}, errorMsg={}",
+                        orderId, payScoreResult.getErrorCode(), payScoreResult.getErrorMsg());
+                return RefundResult.builder()
+                        .success(false)
+                        .errorCode(payScoreResult.getErrorCode())
+                        .errorMsg(payScoreResult.getErrorMsg())
+                        .build();
+            }
+        }
+
+        // 7. 如果 channel 为 null 或 BALANCE,直接更新退款状态(余额退款不经过第三方)
         if (channel == null || channel == PaymentChannel.BALANCE) {
             log.info("余额退款,直接更新订单状态: orderId={}", orderId);
             updateOrderRefundStatus(order, reason, null);

+ 38 - 0
haha-service/src/main/java/com/haha/service/payment/payscore/PayScoreModifyRequest.java

@@ -0,0 +1,38 @@
+package com.haha.service.payment.payscore;
+
+import lombok.Data;
+
+import java.math.BigDecimal;
+import java.util.List;
+
+/**
+ * 修改支付分订单金额请求
+ * 仅 USER_PAYING 状态可调用,仅可下调金额
+ */
+@Data
+public class PayScoreModifyRequest {
+
+    /** 商户服务订单号 */
+    private String outOrderNo;
+
+    /** 修改后的后付费项目列表 */
+    private List<PostPayment> postPayments;
+
+    /** 修改后的总金额(元) */
+    private BigDecimal totalAmount;
+
+    /** 修改原因 */
+    private String reason;
+
+    @Data
+    public static class PostPayment {
+        /** 付费项目名称(不超过20个字符) */
+        private String name;
+        /** 金额(元) */
+        private BigDecimal amount;
+        /** 描述(不超过30个字符) */
+        private String description;
+        /** 数量(1-100) */
+        private Integer count;
+    }
+}

+ 25 - 0
haha-service/src/main/java/com/haha/service/payment/payscore/PayScoreRefundRequest.java

@@ -0,0 +1,25 @@
+package com.haha.service.payment.payscore;
+
+import lombok.Data;
+
+import java.math.BigDecimal;
+
+/**
+ * 支付分退款请求
+ * 使用微信支付分独立退款接口
+ */
+@Data
+public class PayScoreRefundRequest {
+
+    /** 商户服务订单号 */
+    private String outOrderNo;
+
+    /** 退款金额(元) */
+    private BigDecimal refundAmount;
+
+    /** 退款原因 */
+    private String reason;
+
+    /** 商户退款单号(不传则自动生成) */
+    private String outRefundNo;
+}

+ 43 - 3
haha-service/src/main/java/com/haha/service/payment/payscore/PayScoreService.java

@@ -83,11 +83,11 @@ public interface PayScoreService {
     /**
      * 同步订单支付分状态
      * 从微信侧查询最新状态并更新本地订单
-     * 
+     *
      * @param orderId 本地订单ID
-     * @return 是否同步成功
+     * @return 同步结果(含最新状态信息)
      */
-    boolean syncPayScoreStatus(Long orderId);
+    PayScoreResult syncPayScoreStatus(Long orderId);
 
     /**
      * 检查用户支付分开通状态
@@ -204,4 +204,44 @@ public interface PayScoreService {
      * @return 是否同步成功
      */
     boolean syncUserPayScoreStatus(Long userId, Long orderId);
+
+    /**
+     * 修改支付分订单金额(仅 USER_PAYING 状态可下调)
+     *
+     * @param orderId 本地订单ID
+     * @param totalAmount 新金额
+     * @param payments 修改后的后付费项目列表
+     * @param reason 修改原因
+     * @return 支付分结果
+     */
+    PayScoreResult modifyPayScoreOrder(Long orderId, BigDecimal totalAmount,
+            List<PayScoreModifyRequest.PostPayment> payments, String reason);
+
+    /**
+     * 同步订单支付信息(用户已通过其他方式支付时通知微信停止扣款)
+     *
+     * @param orderId 本地订单ID
+     * @param detail 同步说明
+     * @return 支付分结果
+     */
+    PayScoreResult syncOrderPaidInfo(Long orderId, String detail);
+
+    /**
+     * 支付分退款
+     *
+     * @param orderId 本地订单ID
+     * @param refundAmount 退款金额
+     * @param reason 退款原因
+     * @return 支付分结果
+     */
+    PayScoreResult refundPayScoreOrder(Long orderId, BigDecimal refundAmount, String reason);
+
+    /**
+     * 查询支付分退款状态
+     *
+     * @param orderId 本地订单ID
+     * @param outRefundNo 商户退款单号
+     * @return 支付分结果
+     */
+    PayScoreResult queryPayScoreRefund(Long orderId, String outRefundNo);
 }

+ 20 - 0
haha-service/src/main/java/com/haha/service/payment/payscore/PayScoreStrategy.java

@@ -34,6 +34,26 @@ public interface PayScoreStrategy {
      */
     PayScoreResult queryServiceOrder(String outOrderNo);
 
+    /**
+     * 修改订单金额(仅 USER_PAYING 状态可调用,仅可下调)
+     */
+    PayScoreResult modifyServiceOrder(PayScoreModifyRequest request);
+
+    /**
+     * 同步订单信息(用户已通过其他方式支付时,同步给微信停止扣款)
+     */
+    PayScoreResult syncServiceOrder(PayScoreSyncRequest request);
+
+    /**
+     * 申请退款
+     */
+    PayScoreResult refundServiceOrder(PayScoreRefundRequest request);
+
+    /**
+     * 查询退款状态
+     */
+    PayScoreResult queryRefund(String outOrderNo, String outRefundNo);
+
     /**
      * 解析支付分回调通知
      */

+ 20 - 0
haha-service/src/main/java/com/haha/service/payment/payscore/PayScoreSyncRequest.java

@@ -0,0 +1,20 @@
+package com.haha.service.payment.payscore;
+
+import lombok.Data;
+
+/**
+ * 同步支付分订单信息请求
+ * 场景:用户已通过其他方式支付,同步给微信停止扣款
+ */
+@Data
+public class PayScoreSyncRequest {
+
+    /** 商户服务订单号 */
+    private String outOrderNo;
+
+    /** 同步类型:ORDER_PAID 表示订单已支付 */
+    private String type;
+
+    /** 同步详情说明(选填) */
+    private String detail;
+}

+ 176 - 10
haha-service/src/main/java/com/haha/service/payment/payscore/impl/PayScoreServiceImpl.java

@@ -293,12 +293,21 @@ public class PayScoreServiceImpl implements PayScoreService {
             if (result.getTotalAmount() != null) {
                 order.setTotalAmount(result.getTotalAmount());
             }
-            log.info("[支付分服务] 用户完成支付 - orderId: {}, amount: {}元", 
+            log.info("[支付分服务] 用户完成支付 - orderId: {}, amount: {}元",
+                    order.getId(), result.getTotalAmount());
+        } else if ("PAYSCORE.REFUND_PAID".equals(eventType)) {
+            order.setRefundStatus("REFUNDED");
+            order.setPayStatus(OrderConstants.PAY_STATUS_REFUND);
+            order.setRefundTime(result.getPayTime() != null ? result.getPayTime() : LocalDateTime.now());
+            if (result.getTotalAmount() != null) {
+                order.setRefundAmount(result.getTotalAmount());
+            }
+            log.info("[支付分服务] 退款成功回调 - orderId: {}, amount: {}元",
                     order.getId(), result.getTotalAmount());
         } else if (PayScoreState.isPaying(state)) {
             // 订单进入 USER_PAYING 状态(扣款已发起但用户尚未完成支付)
             order.setPayScoreState(PayScoreState.USER_PAYING.getCode());
-            log.warn("[支付分服务] 订单进入用户支付中状态 - orderId: {}, state: {}", 
+            log.warn("[支付分服务] 订单进入用户支付中状态 - orderId: {}, state: {}",
                     order.getId(), state);
         }
 
@@ -309,27 +318,27 @@ public class PayScoreServiceImpl implements PayScoreService {
 
     @Override
     @Transactional(rollbackFor = Exception.class)
-    public boolean syncPayScoreStatus(Long orderId) {
+    public PayScoreResult syncPayScoreStatus(Long orderId) {
         log.info("[支付分服务] 同步订单状态 - orderId: {}", orderId);
 
         PayScoreResult checkResult = checkServiceAvailable();
         if (checkResult != null) {
-            return false;
+            return null;
         }
 
         Order order = validateAndGetOrder(orderId);
 
         if (order.getPayScoreOrderId() == null) {
             log.warn("[支付分服务] 订单未创建支付分服务订单,无法同步 - orderId: {}", orderId);
-            return false;
+            return null;
         }
 
         PayScoreResult result = payScoreStrategy.queryServiceOrder(order.getPayScoreOrderId());
 
         if (!result.isSuccess()) {
-            log.error("[支付分服务] 查询服务订单失败 - orderId: {}, errorCode: {}", 
+            log.error("[支付分服务] 查询服务订单失败 - orderId: {}, errorCode: {}",
                     orderId, result.getErrorCode());
-            return false;
+            return null;
         }
 
         String newState = result.getState();
@@ -347,16 +356,172 @@ public class PayScoreServiceImpl implements PayScoreService {
                 }
             } else if (PayScoreState.isRevoked(newState)) {
                 order.setStatus(OrderStatus.CANCELLED.getCode());
+            } else if (PayScoreState.isExpired(newState)) {
+                order.setStatus(OrderStatus.CANCELLED.getCode());
+                log.info("[支付分服务] 订单已过期 - orderId: {}, outOrderNo: {}", orderId, order.getPayScoreOrderId());
             } else if (PayScoreState.isPaying(newState)) {
                 log.info("[支付分服务] 订单仍处于用户支付中状态 - orderId: {}", orderId);
             }
 
             orderService.updateById(order);
-            log.info("[支付分服务] 订单状态已更新 - orderId: {}, oldState: {}, newState: {}", 
+            log.info("[支付分服务] 订单状态已更新 - orderId: {}, oldState: {}, newState: {}",
                     orderId, oldState, newState);
         }
 
-        return true;
+        return result;
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public PayScoreResult modifyPayScoreOrder(Long orderId, BigDecimal totalAmount,
+            List<PayScoreModifyRequest.PostPayment> payments, String reason) {
+        log.info("[支付分服务] 修改订单金额 - orderId: {}, 新金额: {}元", orderId, totalAmount);
+
+        PayScoreResult checkResult = checkServiceAvailable();
+        if (checkResult != null) {
+            return checkResult;
+        }
+
+        Order order = validateAndGetOrder(orderId);
+
+        if (order.getPayScoreOrderId() == null) {
+            return PayScoreResult.fail("ORDER_NOT_CREATED", "该订单未创建支付分服务订单");
+        }
+
+        if (!PayScoreState.USER_PAYING.getCode().equals(order.getPayScoreState())) {
+            return PayScoreResult.fail("INVALID_STATE", "仅待支付状态的订单可修改金额,当前状态: " + order.getPayScoreState());
+        }
+
+        PayScoreModifyRequest request = new PayScoreModifyRequest();
+        request.setOutOrderNo(order.getPayScoreOrderId());
+        request.setTotalAmount(totalAmount);
+        request.setPostPayments(payments);
+        request.setReason(reason != null ? reason : "金额调整");
+
+        PayScoreResult result = payScoreStrategy.modifyServiceOrder(request);
+
+        if (result.isSuccess()) {
+            if (result.getState() != null) {
+                order.setPayScoreState(result.getState());
+            }
+            order.setTotalAmount(totalAmount);
+            orderService.updateById(order);
+            log.info("[支付分服务] 修改金额成功 - orderId: {}", orderId);
+        }
+
+        return result;
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public PayScoreResult syncOrderPaidInfo(Long orderId, String detail) {
+        log.info("[支付分服务] 同步订单支付信息 - orderId: {}, detail: {}", orderId, detail);
+
+        PayScoreResult checkResult = checkServiceAvailable();
+        if (checkResult != null) {
+            return checkResult;
+        }
+
+        Order order = validateAndGetOrder(orderId);
+
+        if (order.getPayScoreOrderId() == null) {
+            return PayScoreResult.fail("ORDER_NOT_CREATED", "该订单未创建支付分服务订单");
+        }
+
+        if (PayScoreState.isFinished(order.getPayScoreState())) {
+            return PayScoreResult.fail("ORDER_FINISHED", "该订单已完结或已取消");
+        }
+
+        PayScoreSyncRequest request = new PayScoreSyncRequest();
+        request.setOutOrderNo(order.getPayScoreOrderId());
+        request.setType("ORDER_PAID");
+        request.setDetail(detail != null ? detail : "用户已通过其他方式支付");
+
+        PayScoreResult result = payScoreStrategy.syncServiceOrder(request);
+
+        if (result.isSuccess()) {
+            if (result.getState() != null) {
+                order.setPayScoreState(result.getState());
+            }
+            order.setPayStatus(PayStatus.PAID.getCode());
+            order.setStatus(OrderStatus.COMPLETED.getCode());
+            order.setPayTime(LocalDateTime.now());
+            order.setPayScoreFailReason(null);
+            orderService.updateById(order);
+            log.info("[支付分服务] 同步支付信息成功 - orderId: {}", orderId);
+        }
+
+        return result;
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public PayScoreResult refundPayScoreOrder(Long orderId, BigDecimal refundAmount, String reason) {
+        log.info("[支付分服务] 发起退款 - orderId: {}, 金额: {}元", orderId, refundAmount);
+
+        PayScoreResult checkResult = checkServiceAvailable();
+        if (checkResult != null) {
+            return checkResult;
+        }
+
+        Order order = validateAndGetOrder(orderId);
+
+        if (order.getPayScoreOrderId() == null) {
+            return PayScoreResult.fail("ORDER_NOT_CREATED", "该订单未创建支付分服务订单");
+        }
+
+        if (!OrderConstants.PAY_STATUS_PAID.equals(order.getPayStatus())) {
+            return PayScoreResult.fail("ORDER_NOT_PAID", "只有已支付订单可以退款");
+        }
+
+        // 检查 7 天退款期限
+        if (order.getPayTime() != null) {
+            long daysDiff = java.time.temporal.ChronoUnit.DAYS.between(order.getPayTime(), LocalDateTime.now());
+            if (daysDiff > 7) {
+                return PayScoreResult.fail("REFUND_EXPIRED", "订单已完成超过7天,不支持退款");
+            }
+        }
+
+        // 退款金额未传则默认使用实付金额
+        BigDecimal actualRefundAmount = refundAmount != null ? refundAmount
+                : (order.getPaidAmount() != null ? order.getPaidAmount() : order.getTotalAmount());
+
+        PayScoreRefundRequest request = new PayScoreRefundRequest();
+        request.setOutOrderNo(order.getPayScoreOrderId());
+        request.setRefundAmount(actualRefundAmount);
+        request.setReason(reason != null ? reason : "用户申请退款");
+
+        PayScoreResult result = payScoreStrategy.refundServiceOrder(request);
+
+        if (result.isSuccess()) {
+            order.setRefundStatus("REFUNDED");
+            order.setRefundAmount(actualRefundAmount);
+            order.setRefundTime(LocalDateTime.now());
+            order.setRefundReason(reason);
+            order.setPayStatus(OrderConstants.PAY_STATUS_REFUND);
+            orderService.updateById(order);
+            log.info("[支付分服务] 退款成功 - orderId: {}, refundId: {}", orderId, result.getPackageStr());
+        }
+
+        return result;
+    }
+
+    @Override
+    public PayScoreResult queryPayScoreRefund(Long orderId, String outRefundNo) {
+        log.info("[支付分服务] 查询退款状态 - orderId: {}, refundNo: {}", orderId, outRefundNo);
+
+        PayScoreResult checkResult = checkServiceAvailable();
+        if (checkResult != null) {
+            return checkResult;
+        }
+
+        Order order = validateAndGetOrder(orderId);
+
+        if (order.getPayScoreOrderId() == null) {
+            return PayScoreResult.fail("ORDER_NOT_CREATED", "该订单未创建支付分服务订单");
+        }
+
+        return payScoreStrategy.queryRefund(order.getPayScoreOrderId(), outRefundNo);
     }
 
     private PayScoreResult checkServiceAvailable() {
@@ -526,7 +691,8 @@ public class PayScoreServiceImpl implements PayScoreService {
         if (order == null || !order.getUserId().equals(userId)) {
             return false;
         }
-        return syncPayScoreStatus(orderId);
+        PayScoreResult result = syncPayScoreStatus(orderId);
+        return result != null && result.isSuccess();
     }
 
     @Override

+ 232 - 0
haha-service/src/main/java/com/haha/service/payment/payscore/impl/WxPayScoreStrategy.java

@@ -430,6 +430,238 @@ public class WxPayScoreStrategy implements PayScoreStrategy {
         }
     }
 
+    @Override
+    public PayScoreResult modifyServiceOrder(PayScoreModifyRequest request) {
+        log.info("[微信支付分] 修改订单金额 - 订单号: {}, 新金额: {}元", request.getOutOrderNo(), request.getTotalAmount());
+
+        PayScoreResult checkResult = checkServiceAvailable();
+        if (checkResult != null) {
+            return checkResult;
+        }
+
+        try {
+            ObjectNode body = objectMapper.createObjectNode();
+            body.put("appid", wxPayConfig.getAppId());
+            body.put("service_id", wxPayConfig.getServiceId());
+
+            if (request.getPostPayments() != null && !request.getPostPayments().isEmpty()) {
+                ArrayNode postPayments = objectMapper.createArrayNode();
+                for (PayScoreModifyRequest.PostPayment payment : request.getPostPayments()) {
+                    ObjectNode paymentObj = objectMapper.createObjectNode();
+                    paymentObj.put("name", payment.getName());
+                    paymentObj.put("amount", yuanToFen(payment.getAmount()));
+                    if (payment.getDescription() != null) {
+                        paymentObj.put("description", payment.getDescription());
+                    }
+                    if (payment.getCount() != null) {
+                        paymentObj.put("count", payment.getCount());
+                    }
+                    postPayments.add(paymentObj);
+                }
+                body.set("post_payments", postPayments);
+            }
+
+            body.put("total_amount", yuanToFen(request.getTotalAmount()));
+            body.put("reason", request.getReason() != null ? request.getReason() : "金额调整");
+
+            String requestBody = objectMapper.writeValueAsString(body);
+            String url = PAY_SCORE_BASE_URL + "/" + request.getOutOrderNo() + "/modify";
+            log.info("[微信支付分] 修改金额请求: url={}, body={}", url, requestBody);
+
+            HttpRequest httpRequest = new HttpRequest.Builder()
+                    .httpMethod(HttpMethod.POST)
+                    .url(url)
+                    .body(new JsonRequestBody.Builder().body(requestBody).build())
+                    .addHeader("Content-Type", MediaType.APPLICATION_JSON.getValue())
+                    .addHeader("Accept", MediaType.APPLICATION_JSON.getValue())
+                    .build();
+
+            HttpResponse<String> response = wxPayHttpClient.execute(httpRequest, String.class);
+            String responseBody = response.getServiceResponse();
+            log.info("[微信支付分] 修改金额响应: body={}", responseBody);
+
+            JsonNode respJson = objectMapper.readTree(responseBody);
+            String outOrderNo = getJsonString(respJson, "out_order_no");
+            String state = getJsonString(respJson, "state");
+
+            return PayScoreResult.success(outOrderNo, state);
+
+        } catch (HttpException e) {
+            log.error("[微信支付分] 修改金额HTTP异常: {}", e.getMessage(), e);
+            return PayScoreResult.fail("NETWORK_ERROR", "网络请求异常: " + e.getMessage());
+        } catch (ServiceException e) {
+            log.error("[微信支付分] 修改金额业务异常: code={}, msg={}", e.getErrorCode(), e.getErrorMessage(), e);
+            return PayScoreResult.fail(e.getErrorCode(), e.getErrorMessage());
+        } catch (MalformedMessageException e) {
+            log.error("[微信支付分] 修改金额消息解析异常: {}", e.getMessage(), e);
+            return PayScoreResult.fail("MESSAGE_ERROR", "消息解析异常: " + e.getMessage());
+        } catch (Exception e) {
+            log.error("[微信支付分] 修改金额未知异常", e);
+            return PayScoreResult.fail("UNKNOWN_ERROR", "修改金额失败: " + e.getMessage());
+        }
+    }
+
+    @Override
+    public PayScoreResult syncServiceOrder(PayScoreSyncRequest request) {
+        log.info("[微信支付分] 同步订单信息 - 订单号: {}, 类型: {}", request.getOutOrderNo(), request.getType());
+
+        PayScoreResult checkResult = checkServiceAvailable();
+        if (checkResult != null) {
+            return checkResult;
+        }
+
+        try {
+            ObjectNode body = objectMapper.createObjectNode();
+            body.put("appid", wxPayConfig.getAppId());
+            body.put("service_id", wxPayConfig.getServiceId());
+            body.put("type", request.getType() != null ? request.getType() : "ORDER_PAID");
+            if (request.getDetail() != null) {
+                body.put("detail", request.getDetail());
+            }
+
+            String requestBody = objectMapper.writeValueAsString(body);
+            String url = PAY_SCORE_BASE_URL + "/" + request.getOutOrderNo() + "/sync";
+            log.info("[微信支付分] 同步订单请求: url={}, body={}", url, requestBody);
+
+            HttpRequest httpRequest = new HttpRequest.Builder()
+                    .httpMethod(HttpMethod.POST)
+                    .url(url)
+                    .body(new JsonRequestBody.Builder().body(requestBody).build())
+                    .addHeader("Content-Type", MediaType.APPLICATION_JSON.getValue())
+                    .addHeader("Accept", MediaType.APPLICATION_JSON.getValue())
+                    .build();
+
+            HttpResponse<String> response = wxPayHttpClient.execute(httpRequest, String.class);
+            String responseBody = response.getServiceResponse();
+            log.info("[微信支付分] 同步订单响应: body={}", responseBody);
+
+            JsonNode respJson = objectMapper.readTree(responseBody);
+            String outOrderNo = getJsonString(respJson, "out_order_no");
+            String state = getJsonString(respJson, "state");
+
+            return PayScoreResult.success(outOrderNo, state);
+
+        } catch (HttpException e) {
+            log.error("[微信支付分] 同步订单HTTP异常: {}", e.getMessage(), e);
+            return PayScoreResult.fail("NETWORK_ERROR", "网络请求异常: " + e.getMessage());
+        } catch (ServiceException e) {
+            log.error("[微信支付分] 同步订单业务异常: code={}, msg={}", e.getErrorCode(), e.getErrorMessage(), e);
+            return PayScoreResult.fail(e.getErrorCode(), e.getErrorMessage());
+        } catch (MalformedMessageException e) {
+            log.error("[微信支付分] 同步订单消息解析异常: {}", e.getMessage(), e);
+            return PayScoreResult.fail("MESSAGE_ERROR", "消息解析异常: " + e.getMessage());
+        } catch (Exception e) {
+            log.error("[微信支付分] 同步订单未知异常", e);
+            return PayScoreResult.fail("UNKNOWN_ERROR", "同步订单失败: " + e.getMessage());
+        }
+    }
+
+    @Override
+    public PayScoreResult refundServiceOrder(PayScoreRefundRequest request) {
+        log.info("[微信支付分] 申请退款 - 订单号: {}, 金额: {}元", request.getOutOrderNo(), request.getRefundAmount());
+
+        PayScoreResult checkResult = checkServiceAvailable();
+        if (checkResult != null) {
+            return checkResult;
+        }
+
+        try {
+            ObjectNode body = objectMapper.createObjectNode();
+            body.put("appid", wxPayConfig.getAppId());
+            body.put("service_id", wxPayConfig.getServiceId());
+            body.put("reason", request.getReason() != null ? request.getReason() : "用户申请退款");
+            body.put("amount", yuanToFen(request.getRefundAmount()));
+
+            String requestBody = objectMapper.writeValueAsString(body);
+            String url = PAY_SCORE_BASE_URL + "/" + request.getOutOrderNo() + "/pay/refund";
+            log.info("[微信支付分] 退款请求: url={}, body={}", url, requestBody);
+
+            HttpRequest httpRequest = new HttpRequest.Builder()
+                    .httpMethod(HttpMethod.POST)
+                    .url(url)
+                    .body(new JsonRequestBody.Builder().body(requestBody).build())
+                    .addHeader("Content-Type", MediaType.APPLICATION_JSON.getValue())
+                    .addHeader("Accept", MediaType.APPLICATION_JSON.getValue())
+                    .build();
+
+            HttpResponse<String> response = wxPayHttpClient.execute(httpRequest, String.class);
+            String responseBody = response.getServiceResponse();
+            log.info("[微信支付分] 退款响应: body={}", responseBody);
+
+            JsonNode respJson = objectMapper.readTree(responseBody);
+            String outOrderNo = getJsonString(respJson, "out_order_no");
+            String refundId = getJsonString(respJson, "refund_id");
+
+            PayScoreResult result = PayScoreResult.success(outOrderNo, null);
+            result.setPackageStr(refundId);
+            return result;
+
+        } catch (HttpException e) {
+            log.error("[微信支付分] 退款HTTP异常: {}", e.getMessage(), e);
+            return PayScoreResult.fail("NETWORK_ERROR", "网络请求异常: " + e.getMessage());
+        } catch (ServiceException e) {
+            log.error("[微信支付分] 退款业务异常: code={}, msg={}", e.getErrorCode(), e.getErrorMessage(), e);
+            return PayScoreResult.fail(e.getErrorCode(), e.getErrorMessage());
+        } catch (MalformedMessageException e) {
+            log.error("[微信支付分] 退款消息解析异常: {}", e.getMessage(), e);
+            return PayScoreResult.fail("MESSAGE_ERROR", "消息解析异常: " + e.getMessage());
+        } catch (Exception e) {
+            log.error("[微信支付分] 退款未知异常", e);
+            return PayScoreResult.fail("UNKNOWN_ERROR", "退款失败: " + e.getMessage());
+        }
+    }
+
+    @Override
+    public PayScoreResult queryRefund(String outOrderNo, String outRefundNo) {
+        log.info("[微信支付分] 查询退款 - 订单号: {}, 退款单号: {}", outOrderNo, outRefundNo);
+
+        PayScoreResult checkResult = checkServiceAvailable();
+        if (checkResult != null) {
+            return checkResult;
+        }
+
+        try {
+            String url = PAY_SCORE_BASE_URL + "/" + outOrderNo + "/pay/refund/" + outRefundNo
+                    + "?service_id=" + wxPayConfig.getServiceId()
+                    + "&appid=" + wxPayConfig.getAppId();
+            log.info("[微信支付分] 查询退款请求: url={}", url);
+
+            HttpRequest httpRequest = new HttpRequest.Builder()
+                    .httpMethod(HttpMethod.GET)
+                    .url(url)
+                    .addHeader("Accept", MediaType.APPLICATION_JSON.getValue())
+                    .build();
+
+            HttpResponse<String> response = wxPayHttpClient.execute(httpRequest, String.class);
+            String responseBody = response.getServiceResponse();
+            log.info("[微信支付分] 查询退款响应: body={}", responseBody);
+
+            JsonNode respJson = objectMapper.readTree(responseBody);
+            String status = getJsonString(respJson, "status");
+
+            PayScoreResult result = PayScoreResult.success(outOrderNo, status);
+
+            if (respJson.has("amount") && !respJson.get("amount").isNull()) {
+                result.setTotalAmount(fenToYuan(respJson.get("amount").asInt()));
+            }
+
+            return result;
+
+        } catch (HttpException e) {
+            log.error("[微信支付分] 查询退款HTTP异常: {}", e.getMessage(), e);
+            return PayScoreResult.fail("NETWORK_ERROR", "网络请求异常: " + e.getMessage());
+        } catch (ServiceException e) {
+            log.error("[微信支付分] 查询退款业务异常: code={}, msg={}", e.getErrorCode(), e.getErrorMessage(), e);
+            return PayScoreResult.fail(e.getErrorCode(), e.getErrorMessage());
+        } catch (MalformedMessageException e) {
+            log.error("[微信支付分] 查询退款消息解析异常: {}", e.getMessage(), e);
+            return PayScoreResult.fail("MESSAGE_ERROR", "消息解析异常: " + e.getMessage());
+        } catch (Exception e) {
+            log.error("[微信支付分] 查询退款未知异常", e);
+            return PayScoreResult.fail("UNKNOWN_ERROR", "查询退款失败: " + e.getMessage());
+        }
+    }
+
     @Override
     public PayScoreCallbackResult parseCallback(Map<String, Object> params) {
         log.info("[微信支付分] 解析回调通知 - 参数Keys: {}", params.keySet());