ab10431001
Co-authored-by: Cursor <cursoragent@cursor.com>
133 lines
3.9 KiB
TypeScript
133 lines
3.9 KiB
TypeScript
import { createHash } from 'crypto';
|
|
import { Injectable, NotImplementedException } from '@nestjs/common';
|
|
import * as QRCode from 'qrcode';
|
|
import type {
|
|
IWechatProvider,
|
|
WechatCodeSession,
|
|
WechatOAuthSession,
|
|
WechatWxaCodeUnlimitedInput,
|
|
} from './wechat.interface';
|
|
|
|
/**
|
|
* preV1 Mock 微信 Provider。
|
|
*
|
|
* 目的:让「微信授权登录」按钮在不接真实微信的情况下走通。前端仍按真实 OAuth 流程
|
|
* (跳转 oauth-url → 回调携带 code),Mock 端将授权 URL 直接回跳并返回稳定 openId。
|
|
* 后续填入 WX_APP_ID/WX_APP_SECRET 并置 MOCK_WECHAT=false 即切换到真实实现。
|
|
*/
|
|
@Injectable()
|
|
export class WechatMockProvider implements IWechatProvider {
|
|
isEnabled() {
|
|
return true;
|
|
}
|
|
|
|
isMock() {
|
|
return true;
|
|
}
|
|
|
|
isPayEnabled() {
|
|
return false;
|
|
}
|
|
|
|
getMchId() {
|
|
return '';
|
|
}
|
|
|
|
/** 由 code 派生稳定 openId,保证同一 code 多次授权指向同一账号 */
|
|
private openIdFromCode(code: string): string {
|
|
return `mockwx_${createHash('md5').update(code).digest('hex').slice(0, 24)}`;
|
|
}
|
|
|
|
async code2Session(code: string): Promise<WechatCodeSession> {
|
|
return { openId: this.openIdFromCode(code), sessionKey: 'mock-session-key' };
|
|
}
|
|
|
|
async oauth2AccessToken(code: string): Promise<WechatOAuthSession> {
|
|
return { openId: this.openIdFromCode(code), accessToken: 'mock-access-token' };
|
|
}
|
|
|
|
async fetchOAuthUserInfo(accessToken: string, openId: string) {
|
|
return {
|
|
openId,
|
|
nickname: 'Mock微信用户',
|
|
headImgUrl: `https://api.dicebear.com/7.x/avataaars/svg?seed=${encodeURIComponent(openId)}`,
|
|
};
|
|
}
|
|
|
|
async createJssdkConfig(url: string) {
|
|
return {
|
|
appId: 'mock-appid',
|
|
timestamp: Math.floor(Date.now() / 1000),
|
|
nonceStr: 'mocknonce',
|
|
signature: 'mocksignature',
|
|
url,
|
|
jsApiList: ['getLocation', 'scanQRCode', 'chooseImage'],
|
|
} as unknown as Awaited<ReturnType<IWechatProvider['createJssdkConfig']>>;
|
|
}
|
|
|
|
/** 直接把授权链接回跳到 redirectUri 并附带 mock code,模拟微信授权完成 */
|
|
buildOAuthUrl(redirectUri: string, state: string): string {
|
|
const sep = redirectUri.includes('?') ? '&' : '?';
|
|
const code = `mockcode_${state || 'default'}`;
|
|
return `${redirectUri}${sep}code=${encodeURIComponent(code)}&state=${encodeURIComponent(state)}`;
|
|
}
|
|
|
|
async getPhoneNumberByCode(): Promise<string> {
|
|
throw new NotImplementedException('Mock 微信不支持获取手机号,请用短信绑定');
|
|
}
|
|
|
|
createJsapiPrepay(): never {
|
|
throw new NotImplementedException('FEATURE_DISABLED');
|
|
}
|
|
|
|
createNativePrepay(): never {
|
|
throw new NotImplementedException('FEATURE_DISABLED');
|
|
}
|
|
|
|
parsePayNotification(): never {
|
|
throw new NotImplementedException('FEATURE_DISABLED');
|
|
}
|
|
|
|
createDomesticRefund(): never {
|
|
throw new NotImplementedException('FEATURE_DISABLED');
|
|
}
|
|
|
|
parseRefundNotification(): never {
|
|
throw new NotImplementedException('FEATURE_DISABLED');
|
|
}
|
|
|
|
/** Mock:用普通二维码 PNG 占位,内容含 scene,便于本地联调上传 OSS */
|
|
async getWxaCodeUnlimited(input: WechatWxaCodeUnlimitedInput): Promise<Buffer> {
|
|
const scene = (input.scene ?? '').trim() || 'mock';
|
|
return QRCode.toBuffer(`mock-wxa://promo?scene=${encodeURIComponent(scene)}`, {
|
|
width: input.width ?? 430,
|
|
margin: 1,
|
|
type: 'png',
|
|
color: { dark: '#1f1a17', light: '#ffffff' },
|
|
});
|
|
}
|
|
|
|
async uploadShippingInfo() {
|
|
return { errcode: 0, errmsg: 'ok' };
|
|
}
|
|
|
|
async getOrderShippingInfo() {
|
|
return {
|
|
errcode: 0,
|
|
errmsg: 'ok',
|
|
orderState: 3,
|
|
finishShipping: true,
|
|
};
|
|
}
|
|
|
|
async getDeliveryList() {
|
|
return [
|
|
{ deliveryId: 'SF', deliveryName: '顺丰速运' },
|
|
{ deliveryId: 'STO', deliveryName: '申通快递' },
|
|
{ deliveryId: 'YTO', deliveryName: '圆通速递' },
|
|
{ deliveryId: 'ZTO', deliveryName: '中通快递' },
|
|
{ deliveryId: 'YD', deliveryName: '韵达速递' },
|
|
];
|
|
}
|
|
}
|