Conversation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
📄 Step 26-2 規格書(商城基礎 - 抽象化 Service)
1. 階段目標
MockPurchaseService(可用)/IapPurchaseService(佔位,未實作)。2. 功能需求
2.1 架構概念
ConfigService載入store.*商品定義(id/name/desc/image/purchase_limit_type/purchase_max_count/ads_pay)。ads_pay=true商品的「看廣告兌換」流程。2.2 介面定義
2.2.1 購買商品與事件
2.2.2 看廣告購買(抽象)
2.2.3 限購與可購買邏輯
2.3 規則與流程
2.3.1 按鈕是否可購買(UI 只看此結果)
讀取
store.[id]:purchase_limit_type == "limited":總購買次數 ≤purchase_max_count才可買。purchase_limit_type == "unlimited":永遠可買。purchase_limit_type == "daily":當日已購買次數 <purchase_max_count才可買;跨日自動重置。purchase_limit_type == "monthly":當月已購買次數 <purchase_max_count才可買;跨月自動重置。purchase_limit_type == "first7"/"first30":安裝起算第 7/30 天內才可買(逾期canBuy=false)。ads_pay = true:UI 按鈕文案顯示「看影片領取」;呼叫
RewardedAdService.show()→ 若rewarded則視同購買成功。ads_pay = false:呼叫
PurchaseService.buy();Mock 2s 後成功;IAP 版未實作(先拋error或unimplemented)。2.3.2 事件流
UI 點「購買」
ads_pay=true→RewardedAdService.show("store_item", productId:id)ads_pay=false→PurchaseService.buy(id)成功(IAP 或 Ads)→
PurchaseService.purchaseStream發出success→PurchaseLimiter.markPurchased(id, now)→ UI 收到成功事件僅 顯示 Toast(本階段不做權益下發)失敗/取消 →
PurchaseService.purchaseStream發出對應事件 → UI 顯示失敗/取消提示2.4 持久化(PurchaseRepository)
game_state_v{save_version}.store{ "purchases": { "card_click_perm": { "total": 1 }, "pack_daily": { "daily": { "date": "2025-09-12", "count": 2 } }, "pack_monthly": { "monthly": { "ym": "2025-09", "count": 1 } } }, "install": { "firstOpenDate": "2025-08-01" } }欄位說明:
total:一次性或無上限型的總次數。daily.date/daily.count:今日日期(以 Asia/Taipei)、今日累積。monthly.ym/monthly.count:本月字串YYYY-MM、本月累積。firstOpenDate:新手期限購判定的起算日(以 Asia/Taipei 轉字串)。2.5 Mock 與 IAP 服務
2.5.1 MockPurchaseService(可用)
queryProducts(ids):回傳 mock 內容(價格1.99、幣別USD)。buy(productId):await 2s→ 發出PurchaseStatus.success。restore():await 1s→ 發出一筆card_click_perm的success事件。2.5.2 IapPurchaseService(佔位)
UnimplementedError()或回error事件。2.5.3 RewardedAdService(Mock)
2.6 UI 接線(不改視覺)
UI 進入商城時:
ConfigService讀商品清單 idsPurchaseService.queryProducts(ids)(若需要顯示價格;無則可略)PurchaseLimiter.availability(id, nowTaipei)決定按鈕 enable 與提示點擊按鈕:
ads_pay=true→RewardedAdService.show(...)ads_pay=false→PurchaseService.buy(id)監聽
purchaseStream:success→PurchaseLimiter.markPurchased(id, now)→ 顯示「成功」Toastcanceled/error→ 顯示「已取消/失敗」Toast3. 驗收標準
✅ 可切換 Service:以依賴注入切換
MockPurchaseService/IapPurchaseService,UI 無需修改即可運作(IAP 版回傳未實作錯誤,不當機)。✅ 按鈕可用性正確:
limited:達到purchase_max_count後按鈕禁用。unlimited:永遠可購買(UI 永遠可按)。daily:當日達上限後禁用;跨日恢復。monthly:當月達上限後禁用;跨月恢復。first7/first30:於新手期內可買,期滿禁用。✅ ads_pay 行為:
ads_pay=true的商品會呼叫RewardedAdService.show();回傳rewarded→ 視同購買成功。✅ 事件回傳:Mock 下單 2 秒後觸發
success;UI 正常接收並顯示提示。✅ 持久化:重啟 App 後,商品的已購次數/當日/當月計數仍正確;跨日/跨月後自動重置視窗計數。
✅ 時區一致:日界線與月界線判定以 Asia/Taipei 為準。
4. 實例化需求測試案例
案例 1:limited 一次性
card_click_perm(limited, max=1)availability.canBuy=false案例 2:daily 限購 + 跨日
pack_daily(daily, max=3),當前日期 D案例 3:monthly 限購 + 跨月
pack_monthly(monthly, max=3),當前月份 M案例 4:first7 / first30 有效期
firstOpenDate=2025-08-01(台北時間)pack_7n_starterstore.unavailable.first7_expired)案例 5:ads_pay 購買
pack_daily(ads_pay=true, daily, max=3)RewardedAdService.show()→ Mock 回傳rewardedPurchaseEvent.success並累計當日次數 +1案例 6:切換 Service
PurchaseService由 Mock 換成 IAP 佔位error/unimplemented事件並顯示錯誤,不噴例外、不閃退案例 7:重啟持久化
pack_daily當日已買 2 次5. 限制與備註
Date/YearMonth錨點,避免裝置時區差異造成錯誤。RewardedAdService未來可替換實際 SDK(AdMob, ironSource…);placement請固定傳"store_item"與productId方便事件追蹤。權益下發、去廣告態、GA4 事件、錯誤碼對應、重試策略、UI 回饋(loading/disabled/錯誤提示文案)。