feat(openai): 显示订阅到期时间
从 /backend-api/accounts/check 的 entitlement.expires_at 提取订阅 到期日期,每次 token 刷新时更新并存入 credentials,前端账号列表 的订阅类型和隐私下方以灰色小字显示(仅非 Free 账号)。 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -137,8 +137,9 @@ type OpenAITokenInfo struct {
|
||||
ChatGPTAccountID string `json:"chatgpt_account_id,omitempty"`
|
||||
ChatGPTUserID string `json:"chatgpt_user_id,omitempty"`
|
||||
OrganizationID string `json:"organization_id,omitempty"`
|
||||
PlanType string `json:"plan_type,omitempty"`
|
||||
PrivacyMode string `json:"privacy_mode,omitempty"`
|
||||
PlanType string `json:"plan_type,omitempty"`
|
||||
SubscriptionExpiresAt string `json:"subscription_expires_at,omitempty"`
|
||||
PrivacyMode string `json:"privacy_mode,omitempty"`
|
||||
}
|
||||
|
||||
// ExchangeCode exchanges authorization code for tokens
|
||||
@@ -214,6 +215,8 @@ func (s *OpenAIOAuthService) ExchangeCode(ctx context.Context, input *OpenAIExch
|
||||
tokenInfo.PlanType = userInfo.PlanType
|
||||
}
|
||||
|
||||
s.enrichTokenInfo(ctx, tokenInfo, proxyURL)
|
||||
|
||||
return tokenInfo, nil
|
||||
}
|
||||
|
||||
@@ -259,32 +262,40 @@ func (s *OpenAIOAuthService) RefreshTokenWithClientID(ctx context.Context, refre
|
||||
tokenInfo.PlanType = userInfo.PlanType
|
||||
}
|
||||
|
||||
// 每次刷新都通过 ChatGPT backend-api 获取最新的 plan_type,
|
||||
// 因为账号订阅类型可能每月变化,id_token 中的值是签发时的快照,不一定反映当前状态。
|
||||
if tokenInfo.AccessToken != "" && s.privacyClientFactory != nil {
|
||||
// 从 access_token JWT 中提取 orgID(poid),用于匹配正确的账号
|
||||
orgID := tokenInfo.OrganizationID
|
||||
if orgID == "" {
|
||||
if atClaims, err := openai.DecodeIDToken(tokenInfo.AccessToken); err == nil && atClaims.OpenAIAuth != nil {
|
||||
orgID = atClaims.OpenAIAuth.POID
|
||||
}
|
||||
s.enrichTokenInfo(ctx, tokenInfo, proxyURL)
|
||||
|
||||
return tokenInfo, nil
|
||||
}
|
||||
|
||||
// enrichTokenInfo 通过 ChatGPT backend-api 补全 tokenInfo 并设置隐私(best-effort)。
|
||||
// 从 accounts/check 获取最新 plan_type、subscription_expires_at、email,
|
||||
// 然后尝试关闭训练数据共享。适用于所有获取/刷新 token 的路径。
|
||||
func (s *OpenAIOAuthService) enrichTokenInfo(ctx context.Context, tokenInfo *OpenAITokenInfo, proxyURL string) {
|
||||
if tokenInfo.AccessToken == "" || s.privacyClientFactory == nil {
|
||||
return
|
||||
}
|
||||
|
||||
// 从 access_token JWT 中提取 orgID(poid),用于匹配正确的账号
|
||||
orgID := tokenInfo.OrganizationID
|
||||
if orgID == "" {
|
||||
if atClaims, err := openai.DecodeIDToken(tokenInfo.AccessToken); err == nil && atClaims.OpenAIAuth != nil {
|
||||
orgID = atClaims.OpenAIAuth.POID
|
||||
}
|
||||
if info := fetchChatGPTAccountInfo(ctx, s.privacyClientFactory, tokenInfo.AccessToken, proxyURL, orgID); info != nil {
|
||||
if info.PlanType != "" {
|
||||
tokenInfo.PlanType = info.PlanType
|
||||
}
|
||||
if tokenInfo.Email == "" && info.Email != "" {
|
||||
tokenInfo.Email = info.Email
|
||||
}
|
||||
}
|
||||
if info := fetchChatGPTAccountInfo(ctx, s.privacyClientFactory, tokenInfo.AccessToken, proxyURL, orgID); info != nil {
|
||||
if info.PlanType != "" {
|
||||
tokenInfo.PlanType = info.PlanType
|
||||
}
|
||||
if info.SubscriptionExpiresAt != "" {
|
||||
tokenInfo.SubscriptionExpiresAt = info.SubscriptionExpiresAt
|
||||
}
|
||||
if tokenInfo.Email == "" && info.Email != "" {
|
||||
tokenInfo.Email = info.Email
|
||||
}
|
||||
}
|
||||
|
||||
// 尝试设置隐私(关闭训练数据共享),best-effort
|
||||
if tokenInfo.AccessToken != "" && s.privacyClientFactory != nil {
|
||||
tokenInfo.PrivacyMode = disableOpenAITraining(ctx, s.privacyClientFactory, tokenInfo.AccessToken, proxyURL)
|
||||
}
|
||||
|
||||
return tokenInfo, nil
|
||||
tokenInfo.PrivacyMode = disableOpenAITraining(ctx, s.privacyClientFactory, tokenInfo.AccessToken, proxyURL)
|
||||
}
|
||||
|
||||
// ExchangeSoraSessionToken exchanges Sora session_token to access_token.
|
||||
@@ -568,6 +579,9 @@ func (s *OpenAIOAuthService) BuildAccountCredentials(tokenInfo *OpenAITokenInfo)
|
||||
if tokenInfo.PlanType != "" {
|
||||
creds["plan_type"] = tokenInfo.PlanType
|
||||
}
|
||||
if tokenInfo.SubscriptionExpiresAt != "" {
|
||||
creds["subscription_expires_at"] = tokenInfo.SubscriptionExpiresAt
|
||||
}
|
||||
if strings.TrimSpace(tokenInfo.ClientID) != "" {
|
||||
creds["client_id"] = strings.TrimSpace(tokenInfo.ClientID)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user