SCRUM-251 批閱點數實作分析

JIRA: SCRUM-251 Parent: SCRUM-14 (後台-數值管理頁) 難度: ⭐⭐⭐⭐⭐ (最高) 預估工作量: 7-10 天 涉及系統: WriteAhead API + LTRUST API


功能需求摘要

  1. 批閱扣點機制 - 用戶送出批閱時扣除點數
  2. LTRUST 整合 - 與 LTRUST 平台同步點數狀態
  3. 訂閱制服務 - 支援訂閱方案的點數邏輯
  4. 資料統計 - 點數消耗統計
  5. 財會對帳 - 管理後台交易報表

現有程式碼分析

已完成部分

檔案狀態說明
LtrustService::deductPoints()🟡 Stub方法已定義,有 TODO 註解待實作
UserPointsModel🟢 可用本地點數讀取:getOrCreate(), hasEnoughPoints(), getPoints()
LogsUserPointsModel🟢 可用點數日誌表結構已存在
AnswerController::checkPoints()🟢 可用檢查點數 API

待完成部分

檔案/功能狀態說明
UserPointsModel::deduct()🔴 缺失本地扣點方法不存在
AnswerController::scoring()🟡 不完整評分後無扣點邏輯
PointsTransactionService🔴 缺失點數交易服務需新增
訂閱制表/服務🔴 缺失user_subscriptions 表與相關服務
財會報表 API🔴 缺失後台統計 API

扣點流程設計

┌─────────────────────────────────────────────────────────────────┐
│                        前端送出批閱                              │
└─────────────────────────────────────────────────────────────────┘
                                │
                                ▼
┌─────────────────────────────────────────────────────────────────┐
│  Step 1: 檢查本地點數 (UserPointsModel::hasEnoughPoints)        │
└─────────────────────────────────────────────────────────────────┘
                    │                           │
                    ▼                           ▼
            點數不足 ❌                     點數足夠 ✓
            回傳購買 URL                        │
                                               ▼
┌─────────────────────────────────────────────────────────────────┐
│  Step 2: 呼叫 AES 評分 API (AesService::getScore)               │
└─────────────────────────────────────────────────────────────────┘
                    │                           │
                    ▼                           ▼
            評分失敗 ❌                     評分成功 ✓
            回滾 / 通知                         │
                                               ▼
┌─────────────────────────────────────────────────────────────────┐
│  Step 3: 扣除點數 (Transaction)                                 │
│  3a. 扣 LTRUST 點數 (LtrustService::deductPoints)               │
│  3b. 更新本地點數 (UserPointsModel::deduct)                     │
│  3c. 記錄交易日誌 (LogsUserPointsModel::insert)                 │
└─────────────────────────────────────────────────────────────────┘
                    │                           │
                    ▼                           ▼
            扣點失敗 ❌                     扣點成功 ✓
            標記待處理                     回傳評分結果
            通知管理員

實作 Checklist

Phase 1: 後端核心扣點機制

1.1 完成 LtrustService 扣點 API

檔案: app/Services/External/LtrustService.php

  • 確認 LTRUST API 規格 (endpoint, request/response format)
  • 完成 deductPoints() 實作
  • 新增 getPointsBalance() 查詢餘額方法
  • 新增錯誤處理與重試機制
  • 撰寫單元測試
// 需確認的 LTRUST API 細節
// - 實際 endpoint: /api/vendor/service-adjustment 是否正確?
// - product_code 格式規範
// - 錯誤碼定義

1.2 擴展 UserPointsModel

檔案: app/Models/Web/UserPointsModel.php

  • 新增 deduct(string $email, int $points): bool 方法
  • 新增 addPoints(string $email, int $points): bool 方法
  • 使用樂觀鎖或 SELECT FOR UPDATE 防止併發超扣
  • 新增 syncFromLtrust() 同步方法
// 建議實作
public function deduct(string $email, int $points): bool
{
    $db = \Config\Database::connect();
    $db->transStart();
 
    // 使用悲觀鎖
    $row = $db->query("SELECT * FROM user_points WHERE user_email = ? FOR UPDATE", [$email])->getRowArray();
 
    if ((int)$row['points'] < $points) {
        $db->transRollback();
        return false;
    }
 
    $this->where('user_email', $email)
         ->set('points', 'points - ' . $points, false)
         ->set('total_spent', 'total_spent + ' . $points, false)
         ->update();
 
    $db->transComplete();
    return $db->transStatus();
}

1.3 建立點數交易服務

新檔案: app/Services/PointsTransactionService.php

  • 建立服務類別
  • 實作 processReviewDeduction() 整合扣點流程
  • 實作 logTransaction() 記錄交易日誌
  • 實作補償機制 (LTRUST 扣點成功但本地失敗時)

1.4 整合扣點到評分流程

檔案: app/Controllers/Web/AnswerController.php

  • 修改 scoring() 方法
  • 評分成功後呼叫扣點服務
  • 處理扣點失敗情況
// 修改後的 scoring() 流程
public function scoring()
{
    // ... 現有驗證邏輯 ...
 
    // Step 1: 再次檢查點數 (防止併發)
    if (!$this->userPointsModel->hasEnoughPoints($userEmail)) {
        return $this->respond([
            'code' => 402,
            'message' => '點數不足',
            'redirect_url' => env('POINTS_PURCHASE_URL'),
        ]);
    }
 
    // Step 2: 呼叫評分 API
    $result = $this->aesService->getScore($data['question'], $data['text']);
 
    if (!$result['success']) {
        return $this->fail('評分服務暫時無法使用');
    }
 
    // Step 3: 扣除點數 (新增)
    $deductResult = $this->pointsService->processReviewDeduction(
        $userEmail,
        $data['id'],
        1 // 扣除點數
    );
 
    if (!$deductResult['success']) {
        // 評分成功但扣點失敗 - 記錄待處理
        log_message('error', "扣點失敗: {$userEmail}, answer_id: {$data['id']}");
    }
 
    // Step 4: 儲存評分結果
    $this->model->edit([...]);
 
    return $this->respond([...]);
}

Phase 2: 點數日誌與統計

2.1 完善 LogsUserPointsModel

檔案: app/Models/Web/LogsUserPointsModel.php

  • 定義 type 常數 (EARN, SPEND, REFUND, SYNC)
  • 定義 source 常數 (REVIEW, PURCHASE, SUBSCRIPTION, ADMIN_ADJUST)
  • 新增 logDeduction() 便捷方法
  • 新增查詢統計方法
// 建議常數定義
const TYPE_EARN = 'earn';
const TYPE_SPEND = 'spend';
const TYPE_REFUND = 'refund';
const TYPE_SYNC = 'sync';
 
const SOURCE_REVIEW = 'review';
const SOURCE_PURCHASE = 'purchase';
const SOURCE_SUBSCRIPTION = 'subscription';
const SOURCE_ADMIN_ADJUST = 'admin_adjust';

2.2 後台統計 API

新檔案: app/Controllers/Admin/PointsController.php

  • getTransactionList() - 交易明細列表
  • getSummary() - 統計摘要 (總消耗、總儲值等)
  • exportReport() - 匯出財會報表

Phase 3: 訂閱制服務

3.1 確認訂閱需求

  • 與 PM 確認訂閱方案內容
  • 確認 LTRUST 訂閱 API 規格
  • 確認訂閱與點數的關係 (訂閱用戶免點數? 訂閱贈點?)

3.2 資料庫設計

新表: user_subscriptions

  • 設計表結構
  • 建立 Migration
-- 建議表結構
CREATE TABLE user_subscriptions (
    id INT PRIMARY KEY AUTO_INCREMENT,
    user_email VARCHAR(255) NOT NULL,
    plan_code VARCHAR(50) NOT NULL,
    ltrust_subscription_id VARCHAR(100),
    status ENUM('active', 'expired', 'cancelled') NOT NULL,
    started_at DATETIME NOT NULL,
    expires_at DATETIME NOT NULL,
    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
    updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    INDEX idx_user_status (user_email, status),
    INDEX idx_expires (expires_at)
);

3.3 訂閱服務實作

新檔案: app/Services/SubscriptionService.php

  • hasActiveSubscription(string $email): bool
  • syncSubscriptionFromLtrust(string $email)
  • 整合到扣點邏輯 (訂閱用戶跳過扣點)

Phase 4: 前端優化

4.1 點數不足提示 UX

Codebase: writeahead-web

  • 優化 app/pages/practice/[id].vue 點數不足提示
  • 顯示目前剩餘點數
  • 提供明確購買按鈕/連結
  • 考慮 Toast 或 Modal 形式

Phase 5: 後台財會頁面 (Optional)

Codebase: writeahead-admin-web

  • 建立 app/pages/finance/points.vue 頁面
  • 交易明細表格
  • 統計摘要卡片
  • 匯出功能

關鍵挑戰與解決方案

挑戰風險等級解決方案
LTRUST API 規格未確認🔴 高需與 LTRUST 團隊確認,這是阻塞項
交易一致性🟡 中DB Transaction + 補償機制 + 待處理佇列
併發超扣🟡 中SELECT FOR UPDATE 悲觀鎖
訂閱制邏輯不明🔴 高需 PM 確認訂閱方案細節
LTRUST 與本地點數不同步🟡 中定期同步 + 每次登入同步

依賴與阻塞項

外部依賴

項目負責方狀態說明
LTRUST 扣點 API 規格LTRUST 團隊⏳ 待確認需確認 endpoint、錯誤碼、測試環境
LTRUST 訂閱 API 規格LTRUST 團隊⏳ 待確認訂閱狀態查詢、webhook 通知
訂閱方案定義PM⏳ 待確認方案內容、與點數關係

建議開發順序

Week 1:
├── [Day 1-2] 確認 LTRUST API 規格 (阻塞項)
├── [Day 3-4] Phase 1.1-1.2: 完成 LtrustService & UserPointsModel
└── [Day 5] Phase 1.3: 建立 PointsTransactionService

Week 2:
├── [Day 1-2] Phase 1.4: 整合扣點到 scoring()
├── [Day 3-4] Phase 2: 日誌與統計
└── [Day 5] Phase 4: 前端 UX 優化

Week 3 (如訂閱需求確認):
├── Phase 3: 訂閱制服務
└── Phase 5: 後台財會頁面 (Optional)

測試要點

單元測試

  • LtrustService::deductPoints() - Mock HTTP 測試
  • UserPointsModel::deduct() - 併發測試
  • PointsTransactionService - 各種失敗情境

整合測試

  • 完整批閱流程 (點數足夠)
  • 點數不足情境
  • LTRUST API 失敗情境
  • 併發批閱測試

手動測試

  • 點數不足時 UX 流程
  • 購買點數後返回繼續批閱
  • 後台財會報表資料正確性

關鍵檔案路徑

WriteAhead 後端 (/home/matt/Github/writeahead-main-api)

app/Services/External/LtrustService.php      # LTRUST 整合服務
app/Models/Web/UserPointsModel.php           # 點數 Model
app/Models/Web/LogsUserPointsModel.php       # 點數日誌 Model
app/Controllers/Web/AnswerController.php     # 批閱控制器
app/Controllers/Web/AuthController.php       # 登入時取得點數

# 待建立
app/Services/PointsTransactionService.php    # 點數交易服務
app/Services/SubscriptionService.php         # 訂閱服務
app/Controllers/Admin/PointsController.php   # 後台財會 API
app/Models/Web/UserSubscriptionsModel.php    # 訂閱 Model

WriteAhead 前端 (/home/matt/Github/writeahead-web)

app/pages/practice/[id].vue                  # 練習頁 (需優化點數 UX)
app/composables/usePractice.ts               # 練習相關邏輯

相關筆記

  • WriteAhead Sprint 4 工程分析報告 - Sprint 4 總覽
  • (待建立) LTRUST 整合技術文件
  • (待建立) WriteAhead 點數系統架構

附錄:JIRA 附件

此 issue 包含 3 張附件圖片 (需查看以了解完整需求):

  • image-20260205-065332.png
  • image-20260205-065722.png
  • image-20260205-065823.png

更新紀錄

日期更新內容
2026-02-06初始分析,建立完整 checklist