{"record":{"id":"c744eae11ee8afd9","repo":"yikart/AiToEarn","slug":"channelaccesstokenfailed-c744ea","errorCode":"ChannelAccessTokenFailed","errorMessage":"ResponseCode.ChannelAccessTokenFailed","messagePattern":"ResponseCode\\.ChannelAccessTokenFailed","errorType":"exception","errorClass":"AppException","httpStatus":null,"severity":"error","filePath":"project/aitoearn-backend/apps/aitoearn-server/src/core/channels/platforms/douyin/douyin-auth.provider.ts","lineNumber":151,"sourceCode":"      raw: {\n        openId: userInfo.openId,\n        unionId: userInfo.unionId,\n        nickname: userInfo.nickname,\n        city: userInfo.city,\n        province: userInfo.province,\n        country: userInfo.country,\n        eAccountRole: userInfo.eAccountRole,\n      },\n    }\n  }\n\n  private async exchangeMiniAppCallback(\n    input: AuthCallbackInput,\n    callback: DouyinMiniAppCallback,\n  ): Promise<CredentialResult> {\n    const sessionInfo = await this.douyinMiniAppService.code2Session(callback.token)\n    if (!sessionInfo?.openid) {\n      throw new AppException(ResponseCode.ChannelAccessTokenFailed)\n    }\n    const openId = sessionInfo.openid\n    const unionId = sessionInfo.unionid\n\n    if (this.config.authType !== AuthType.QrCode) {\n      throw new AppException(ResponseCode.ChannelAuthorizationFailed)\n    }\n    if (!callback.tickets) {\n      throw new AppException(ResponseCode.ChannelAuthorizationFailed)\n    }\n\n    const userDataTicket = callback.tickets[DOUYIN_MINIAPP_USER_DATA_SCOPE]\n    const videoBindTicket = callback.tickets[DOUYIN_MINIAPP_VIDEO_BIND_SCOPE]\n    const homepageCredential = await this.douyinMiniAppService.getUserAccessToken(userDataTicket)\n    const videoCredential = userDataTicket === videoBindTicket\n      ? homepageCredential\n      : await this.douyinMiniAppService.getUserAccessToken(videoBindTicket)\n","sourceCodeStart":133,"sourceCodeEnd":169,"githubUrl":"https://github.com/yikart/AiToEarn/blob/d3aa8bea5b146a8675607cf0144d891aad3e9683/project/aitoearn-backend/apps/aitoearn-server/src/core/channels/platforms/douyin/douyin-auth.provider.ts#L133-L169","documentation":"During the Douyin mini-app callback exchange, exchangeMiniAppCallback calls code2Session(callback.token) and requires a session containing an openid. If the session is null or has no openid, the app cannot identify the Douyin user, so it throws ChannelAccessTokenFailed — the code-to-session exchange did not yield a usable access identity.","triggerScenarios":"Mini-app callback where the temporary `token` is invalid, expired, or already consumed, causing douyinMiniAppService.code2Session to return null/undefined or a session without openid.","commonSituations":"User delayed completing the mini-app flow past token TTL, replayed callback (token single-use), wrong mini-app appid/secret configured so Douyin rejects the session lookup, or network failure silently swallowed upstream.","solutions":["Have the user restart the mini-app authorization to generate a fresh callback token.","Verify the Douyin mini-app appid/secret in config match the app that produced the callback.","Check code2Session for error details (Douyin returns errcode/errmsg) and log them at the service layer.","Ensure callbacks are processed once and promptly; do not retry with the same consumed token."],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"try {\n  await douyinAuth.exchangeCode({ token: callbackToken, ... })\n} catch (e) {\n  if (e.code === 'ChannelAccessTokenFailed') {\n    // token invalid/expired/consumed: do NOT retry with the same token\n    return { error: 'session expired, please restart mini-app authorization' }\n  }\n  throw e\n}","preventionTips":["Process mini-app callbacks immediately; the token is short-lived and single-use.","Never replay or cache callback tokens across attempts.","Verify mini-app appid/secret per environment (CN vs Intl) before deploy.","Log code2Session errcode/errmsg at the service layer for diagnosis."],"tags":["douyin","miniapp","oauth","code2session"],"backgroundTag":"code2session-failed","analyzedSha":"d3aa8bea5b146a8675607cf0144d891aad3e9683","analyzedAt":"2026-08-31T14:19:24.185Z","schemaVersion":2},"datasetVersion":"2026-08-31T19:17:28.585Z"}