@@ -11,15 +11,26 @@ import {
1111 UnauthorizedException ,
1212} from '@nestjs/common' ;
1313
14+ export interface WechatUserInfo {
15+ unionid : string ;
16+ nickname : string ;
17+ openid : string ;
18+ }
19+
1420@Injectable ( )
1521export class WechatService extends SocialService {
1622 private readonly logger = new Logger ( WechatService . name ) ;
1723
1824 private readonly appId : string ;
1925 private readonly appSecret : string ;
20- private readonly redirectUri : string ;
2126 private readonly openAppId : string ;
2227 private readonly openAppSecret : string ;
28+ private readonly oldAppId : string ;
29+ private readonly oldAppSecret : string ;
30+ private readonly oldOpenAppId : string ;
31+ private readonly oldOpenAppSecret : string ;
32+ private readonly redirectUri : string ;
33+ private readonly migrationRedirectUri : string ;
2334
2435 constructor (
2536 private readonly configService : ConfigService ,
@@ -34,10 +45,6 @@ export class WechatService extends SocialService {
3445 'OBB_WECHAT_APP_SECRET' ,
3546 '' ,
3647 ) ;
37- this . redirectUri = this . configService . get < string > (
38- 'OBB_WECHAT_REDIRECT_URI' ,
39- '' ,
40- ) ;
4148 this . openAppId = this . configService . get < string > (
4249 'OBB_OPEN_WECHAT_APP_ID' ,
4350 '' ,
@@ -46,6 +53,27 @@ export class WechatService extends SocialService {
4653 'OBB_OPEN_WECHAT_APP_SECRET' ,
4754 '' ,
4855 ) ;
56+ this . oldAppId = this . configService . get < string > ( 'OBB_WECHAT_OLD_APP_ID' , '' ) ;
57+ this . oldAppSecret = this . configService . get < string > (
58+ 'OBB_WECHAT_OLD_APP_SECRET' ,
59+ '' ,
60+ ) ;
61+ this . oldOpenAppId = this . configService . get < string > (
62+ 'OBB_OPEN_WECHAT_OLD_APP_ID' ,
63+ '' ,
64+ ) ;
65+ this . oldOpenAppSecret = this . configService . get < string > (
66+ 'OBB_OPEN_WECHAT_OLD_APP_SECRET' ,
67+ '' ,
68+ ) ;
69+ this . redirectUri = this . configService . get < string > (
70+ 'OBB_WECHAT_REDIRECT_URI' ,
71+ '' ,
72+ ) ;
73+ this . migrationRedirectUri = this . configService . get < string > (
74+ 'OBB_WECHAT_MIGRATION_REDIRECT_URI' ,
75+ '' ,
76+ ) ;
4977 }
5078
5179 available ( ) {
@@ -173,6 +201,72 @@ export class WechatService extends SocialService {
173201 } ) ;
174202 }
175203
204+ migrationQrCode ( type : 'new' | 'old' = 'new' ) {
205+ const isOld = type === 'old' ;
206+ const state = this . setState ( 'open_weixin' , type ) ;
207+ this . cleanExpiresState ( ) ;
208+ return {
209+ state,
210+ appId : isOld ? this . oldOpenAppId : this . openAppId ,
211+ scope : 'snsapi_login' ,
212+ redirectUri : encodeURIComponent ( this . migrationRedirectUri ) ,
213+ } ;
214+ }
215+
216+ migrationAuthUrl ( type : 'new' | 'old' = 'new' ) : string {
217+ const isOld = type === 'old' ;
218+ const state = this . setState ( 'weixin' , type ) ;
219+ this . cleanExpiresState ( ) ;
220+ return `https://open.weixin.qq.com/connect/oauth2/authorize?appid=${ isOld ? this . oldAppId : this . appId } &redirect_uri=${ encodeURIComponent ( this . migrationRedirectUri ) } &response_type=code&scope=snsapi_userinfo&state=${ state } #wechat_redirect` ;
221+ }
222+
223+ async migrationCallback (
224+ code : string ,
225+ state : string ,
226+ ) : Promise < WechatUserInfo > {
227+ const stateInfo = this . getState ( state ) ;
228+ if ( ! stateInfo ) {
229+ throw new UnauthorizedException ( 'Invalid state identifier' ) ;
230+ }
231+ const isOld = state . startsWith ( 'old_' ) ;
232+ const isWeixin = stateInfo . type === 'weixin' ;
233+ const rawAppid = isOld ? this . oldAppId : this . appId ;
234+ const rawAppsecret = isOld ? this . oldAppSecret : this . appSecret ;
235+ const rawOpenAppid = isOld ? this . oldOpenAppId : this . openAppId ;
236+ const rawOpenAppsecret = isOld ? this . oldOpenAppSecret : this . openAppSecret ;
237+ const appId = isWeixin ? rawAppid : rawOpenAppid ;
238+ const appSecret = isWeixin ? rawAppsecret : rawOpenAppsecret ;
239+ const accessTokenResponse = await fetch (
240+ `https://api.weixin.qq.com/sns/oauth2/access_token?appid=${ appId } &secret=${ appSecret } &code=${ code } &grant_type=authorization_code` ,
241+ ) ;
242+ if ( ! accessTokenResponse . ok ) {
243+ throw new UnauthorizedException ( 'Failed to get WeChat access token' ) ;
244+ }
245+ const accessTokenData = await accessTokenResponse . json ( ) ;
246+
247+ if ( accessTokenData . errmsg ) {
248+ throw new BadRequestException ( accessTokenData . errmsg ) ;
249+ }
250+
251+ const userDataResponse = await fetch (
252+ `https://api.weixin.qq.com/sns/userinfo?access_token=${ accessTokenData . access_token } &openid=${ accessTokenData . openid } &lang=zh_CN` ,
253+ ) ;
254+ if ( ! userDataResponse . ok ) {
255+ throw new UnauthorizedException ( 'Failed to get WeChat user info' ) ;
256+ }
257+ const userData = await userDataResponse . json ( ) ;
258+
259+ if ( userData . errmsg ) {
260+ throw new BadRequestException ( userData . errmsg ) ;
261+ }
262+
263+ return userData ;
264+ }
265+
266+ async migration ( oldUnionid : string , newUnionid : string ) {
267+ await this . userService . updateBinding ( oldUnionid , newUnionid ) ;
268+ }
269+
176270 async unbind ( userId : string ) {
177271 await this . userService . unbindByLoginType ( userId , 'wechat' ) ;
178272 }
0 commit comments