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:
parent
6cd54a3a8f
commit
24eb3e190c
@ -65,7 +65,7 @@ class CommandCodeGoRepository(
|
|||||||
|
|
||||||
// 从订阅信息拿计划名称,查 plan catalog 拿总量
|
// 从订阅信息拿计划名称,查 plan catalog 拿总量
|
||||||
val monthlyTotal = subResult.getOrNull()?.let { sub ->
|
val monthlyTotal = subResult.getOrNull()?.let { sub ->
|
||||||
PLANS[sub.planId.lowercase()]
|
planMonthlyQuota(sub.planId)
|
||||||
}
|
}
|
||||||
val billingPeriodEndMillis = subResult.getOrNull()?.let { parseIsoToEpoch(it.currentPeriodEnd) }
|
val billingPeriodEndMillis = subResult.getOrNull()?.let { parseIsoToEpoch(it.currentPeriodEnd) }
|
||||||
|
|
||||||
@ -187,24 +187,35 @@ class CommandCodeGoRepository(
|
|||||||
} catch (_: Exception) { null }
|
} catch (_: Exception) { null }
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun planDisplayName(planId: String): String =
|
|
||||||
PLAN_NAMES[planId.lowercase()] ?: planId
|
|
||||||
|
|
||||||
companion object {
|
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(
|
private val PLANS = mapOf(
|
||||||
"individual-go" to 10.0,
|
"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-max" to 150.0,
|
||||||
"individual-ultra" to 300.0
|
"individual-ultra" to 300.0
|
||||||
)
|
)
|
||||||
|
|
||||||
private val PLAN_NAMES = mapOf(
|
private val PLAN_NAMES = mapOf(
|
||||||
"individual-go" to "Go",
|
"individual-go" to "Go",
|
||||||
|
"individual-goat" to "GOAT",
|
||||||
"individual-pro" to "Pro",
|
"individual-pro" to "Pro",
|
||||||
"individual-max" to "Max",
|
"individual-max" to "Max",
|
||||||
"individual-ultra" to "Ultra"
|
"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,转为距现在的剩余秒数 */
|
/** API 返回的是 epoch millis,转为距现在的剩余秒数 */
|
||||||
private fun epochToRemainingSec(epochMillis: Long): Long =
|
private fun epochToRemainingSec(epochMillis: Long): Long =
|
||||||
maxOf(0L, (epochMillis - System.currentTimeMillis()) / 1000)
|
maxOf(0L, (epochMillis - System.currentTimeMillis()) / 1000)
|
||||||
|
|||||||
@ -314,7 +314,8 @@ private fun CommandCodeGoUsageCard(state: State) {
|
|||||||
UsageWindowRow(
|
UsageWindowRow(
|
||||||
label = stringResource(R.string.window_monthly),
|
label = stringResource(R.string.window_monthly),
|
||||||
pct = pct,
|
pct = pct,
|
||||||
resetInSec = extras["billingPeriodEnd"]?.let { parseIsoDuration(it) }
|
// 仓库已把账单周期结束转成剩余秒写入 monthly.resetInSec(billingPeriodEnd 是 epoch 毫秒,不能按 ISO 解析)
|
||||||
|
resetInSec = extras["monthly.resetInSec"]?.toLongOrNull()
|
||||||
)
|
)
|
||||||
Spacer(modifier = Modifier.height(4.dp))
|
Spacer(modifier = Modifier.height(4.dp))
|
||||||
Text(
|
Text(
|
||||||
|
|||||||
@ -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))
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user