{"record":{"id":"9f087fea909a9c73","repo":"yikart/AiToEarn","slug":"channelaccesstokenfailed-9f087f","errorCode":"ChannelAccessTokenFailed","errorMessage":"ChannelAccessTokenFailed","messagePattern":"ChannelAccessTokenFailed","errorType":"error_code","errorClass":"AppException","httpStatus":null,"severity":"error","filePath":"project/aitoearn-backend/apps/aitoearn-server/src/core/channels/platforms/douyin/douyin-miniapp.service.ts","lineNumber":99,"sourceCode":"\n  async code2Session(code: string): Promise<DouyinMiniAppSessionInfo> {\n    const miniApp = this.miniAppConfig\n    const response = await this.http.post<DouyinMiniAppCode2SessionResponse>(\n      this.endpoints.code2Session,\n      {\n        appid: miniApp.clientId,\n        secret: miniApp.clientSecret,\n        code,\n      },\n      {\n        headers: {\n          'Content-Type': 'application/json',\n        },\n      },\n    )\n    const data = response.data.data\n    if (!data?.openid) {\n      throw new AppException(ResponseCode.ChannelAccessTokenFailed, { platform: AccountType.Douyin, field: 'openid', reasonCode: 'missing_platform_field' })\n    }\n\n    return {\n      openid: data.openid,\n      unionid: data.unionid,\n      anonymousOpenid: data.anonymous_openid ?? data.anonymousOpenid,\n    }\n  }\n\n  async getUserAccessToken(ticket: string): Promise<DouyinMiniAppAccessTokenInfo> {\n    const miniApp = this.miniAppConfig\n    const response = await this.http.post<DouyinMiniAppAccessTokenResponse>(\n      this.endpoints.userAccessToken,\n      new URLSearchParams({\n        client_key: miniApp.clientId,\n        client_secret: miniApp.clientSecret,\n        code: ticket,\n        grant_type: DouyinOAuthGrantType.AuthorizationCode,","sourceCodeStart":81,"sourceCodeEnd":117,"githubUrl":"https://github.com/yikart/AiToEarn/blob/d3aa8bea5b146a8675607cf0144d891aad3e9683/project/aitoearn-backend/apps/aitoearn-server/src/core/channels/platforms/douyin/douyin-miniapp.service.ts#L81-L117","documentation":"code2Session exchanges a mini-app login code with Douyin and throws ChannelAccessTokenFailed when the response body lacks data.openid. Without openid the session cannot be identified, so the exchange is treated as failed authentication.","triggerScenarios":"Calling code2Session with an expired, already-used, or invalid js_code, or Douyin returning 200 with an error body containing no openid (e.g. invalid code, appid/secret mismatch).","commonSituations":"Mini-app client cached/replayed a login code (codes are single-use), clientId/clientSecret configured for the wrong Douyin app, clock/network issues producing stale codes, or Douyin-side error returned inside a 200 body.","solutions":["Ensure the mini-app requests a fresh login code for every code2Session call (codes are single-use)","Verify clientId/clientSecret match the Douyin mini-app that issued the code","Log response.data to surface Douyin's errcode/errmsg, which explains why openid is missing"],"exampleFix":"// before\nif (!data?.openid) {\n  throw new AppException(ResponseCode.ChannelAccessTokenFailed, { platform: AccountType.Douyin, field: 'openid', reasonCode: 'missing_platform_field' })\n}\n// after\nif (!data?.openid) {\n  throw new AppException(ResponseCode.ChannelAccessTokenFailed, { platform: AccountType.Douyin, field: 'openid', reasonCode: 'missing_platform_field', douyinErrcode: data?.errcode, douyinErrmsg: data?.errmsg })\n}","handlingStrategy":"try-catch","validationCode":"if (!code || typeof code !== 'string') {\n  throw new Error('code2Session requires a fresh, non-empty js_code from the mini-app client')\n}","typeGuard":"function hasOpenid(res: { data?: { data?: { openid?: unknown } } }): res is { data: { data: { openid: string } } } {\n  return typeof res.data?.data?.openid === 'string' && res.data.data.openid.length > 0\n}","tryCatchPattern":"try {\n  const session = await service.code2Session(code)\n} catch (e) {\n  if (e instanceof AppException && e.code === 'ChannelAccessTokenFailed') {\n    // codes are single-use: have the client request a new login code\n    return { retryWithNewCode: true }\n  }\n  throw e\n}","preventionTips":["Always request a fresh login code per exchange; never retry with a consumed code","Verify clientId/clientSecret match the mini-app that generated the code","Log Douyin errcode/errmsg embedded in 200 responses","Detect client clock skew / stale cached codes in the mini-app client"],"tags":["douyin","oauth","token-exchange","third-party"],"backgroundTag":"oauth-code-exchange-failed","analyzedSha":"d3aa8bea5b146a8675607cf0144d891aad3e9683","analyzedAt":"2026-08-31T14:19:24.185Z","schemaVersion":2},"datasetVersion":"2026-08-31T19:17:28.585Z"}