fix(ccgo): GOAT 计划月度额度映射 + 详情页月度重置倒计时修复

- PLANS 新增 individual-goat→$70(官网 GOAT 计划,5h $14 / weekly $35 / monthly $70),
  修正 individual-pro 30→80,planDisplayName 映射 GOAT
- 抽 internal planMonthlyQuota/planDisplayName(大小写不敏感、未知计划安全降级)
- 详情页月度重置倒计时改用仓库已算好的 monthly.resetInSec(原按 ISO 解析
  epoch 毫秒的 billingPeriodEnd 恒失败,倒计时永不显示)
- 新增 7 个计划映射测试
This commit is contained in:
WaterRain 2026-08-30 05:49:52 +00:00
parent 6cd54a3a8f
commit 24eb3e190c
No known key found for this signature in database
3 changed files with 71 additions and 6 deletions

View File

@ -65,7 +65,7 @@ class CommandCodeGoRepository(
// 从订阅信息拿计划名称,查 plan catalog 拿总量
val monthlyTotal = subResult.getOrNull()?.let { sub ->
PLANS[sub.planId.lowercase()]
planMonthlyQuota(sub.planId)
}
val billingPeriodEndMillis = subResult.getOrNull()?.let { parseIsoToEpoch(it.currentPeriodEnd) }
@ -187,24 +187,35 @@ class CommandCodeGoRepository(
} catch (_: Exception) { null }
}
private fun planDisplayName(planId: String): String =
PLAN_NAMES[planId.lowercase()] ?: planId
companion object {
/** 计划 → 月度额度(美元)。2026-08 起官网新计划体系:
* Go $1 → $10、GOAT $10 → $70、Pro $20 → $80、Max 10× $100 → $150、Max 20× $200 → $300。
* 旧值 individual-go=10 与 individual-goat=70 并存(goat 是当前主流计划)。
*/
private val PLANS = mapOf(
"individual-go" to 10.0,
"individual-pro" to 30.0,
"individual-goat" to 70.0,
"individual-pro" to 80.0,
"individual-max" to 150.0,
"individual-ultra" to 300.0
)
private val PLAN_NAMES = mapOf(
"individual-go" to "Go",
"individual-goat" to "GOAT",
"individual-pro" to "Pro",
"individual-max" to "Max",
"individual-ultra" to "Ultra"
)
/** 按 planId 查月度额度(美元);未知计划返回 null(调用方安全降级)。 */
internal fun planMonthlyQuota(planId: String?): Double? =
planId?.lowercase()?.let { PLANS[it] }
/** 按 planId 查展示名;未知计划原样返回。 */
internal fun planDisplayName(planId: String?): String =
planId?.lowercase()?.let { PLAN_NAMES[it] } ?: planId ?: ""
/** API 返回的是 epoch millis,转为距现在的剩余秒数 */
private fun epochToRemainingSec(epochMillis: Long): Long =
maxOf(0L, (epochMillis - System.currentTimeMillis()) / 1000)

View File

@ -314,7 +314,8 @@ private fun CommandCodeGoUsageCard(state: State) {
UsageWindowRow(
label = stringResource(R.string.window_monthly),
pct = pct,
resetInSec = extras["billingPeriodEnd"]?.let { parseIsoDuration(it) }
// 仓库已把账单周期结束转成剩余秒写入 monthly.resetInSec(billingPeriodEnd 是 epoch 毫秒,不能按 ISO 解析)
resetInSec = extras["monthly.resetInSec"]?.toLongOrNull()
)
Spacer(modifier = Modifier.height(4.dp))
Text(

View File

@ -0,0 +1,53 @@
package com.rainy.token.data.repository
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNull
import org.junit.Test
/**
* Unit tests for [CommandCodeGoRepository] 的计划 → 月度额度映射。
*
* 2026-08 官网更新了计划体系(GOAT 等新计划),旧映射缺少
* individual-goat 会导致 GOAT 用户月度总量/已用计算缺失。
*/
class CommandCodeGoPlanMappingTest {
@Test
fun `goat plan maps to 70 usd monthly quota`() {
assertEquals(70.0, CommandCodeGoRepository.planMonthlyQuota("individual-goat")!!, 0.001)
}
@Test
fun `go plan maps to 10 usd monthly quota`() {
assertEquals(10.0, CommandCodeGoRepository.planMonthlyQuota("individual-go")!!, 0.001)
}
@Test
fun `pro plan maps to 80 usd monthly quota`() {
assertEquals(80.0, CommandCodeGoRepository.planMonthlyQuota("individual-pro")!!, 0.001)
}
@Test
fun `max plans map to 150 and 300 usd monthly quota`() {
assertEquals(150.0, CommandCodeGoRepository.planMonthlyQuota("individual-max")!!, 0.001)
assertEquals(300.0, CommandCodeGoRepository.planMonthlyQuota("individual-ultra")!!, 0.001)
}
@Test
fun `unknown plan returns null quota`() {
assertNull(CommandCodeGoRepository.planMonthlyQuota("individual-unknown"))
assertNull(CommandCodeGoRepository.planMonthlyQuota(null))
}
@Test
fun `plan id lookup is case insensitive`() {
assertEquals(70.0, CommandCodeGoRepository.planMonthlyQuota("INDIVIDUAL-GOAT")!!, 0.001)
assertEquals("GOAT", CommandCodeGoRepository.planDisplayName("Individual-Goat"))
}
@Test
fun `unknown plan display name falls back to raw id`() {
assertEquals("mystery-plan", CommandCodeGoRepository.planDisplayName("mystery-plan"))
assertEquals("", CommandCodeGoRepository.planDisplayName(null))
}
}