4f363fa9e7
- HQ stores: partner column uses companyName/name/phone via partnerOptionLabel - Dev plan: version RELEASED auto-marks tasks RELEASED and tickets PUBLISHED with releasedVersionNo - mini-user/h5-shop/h5-partner v3.4.13 UX fixes (store UI, iOS scan OAuth, partner login guard) - Docs: v3.4.13 dev doc + status audit updates Co-authored-by: Cursor <cursoragent@cursor.com>
164 lines
5.9 KiB
TypeScript
164 lines
5.9 KiB
TypeScript
import type { ClientRuntimeConfig, WechatLoginResult } from '@dukang/shared-types';
|
|
import type { PartnerPhoneCheckResponse } from '@dukang/shared-types';
|
|
import { isWxAuthorizeEnabled } from '@dukang/shared-types';
|
|
import { WECHAT_INAPP_REQUIRED_MSG } from '@dukang/weixin-sdk';
|
|
import { isWechatEnv, weixinSdk } from './weixin';
|
|
import { getPartnerProfile, request, saveWechatSession, type PartnerSessionPayload, type PartnerSessionProfile } from './api';
|
|
|
|
export const PARTNER_WECHAT_LOGIN_HINT =
|
|
'请先使用手机验证码登录,登录后将自动关联微信';
|
|
|
|
export type PartnerProfile = {
|
|
id: string;
|
|
name: string;
|
|
phone: string;
|
|
companyName?: string;
|
|
hasWechat?: boolean;
|
|
wxNickname?: string | null;
|
|
wxAvatarUrl?: string | null;
|
|
isPrimary?: boolean;
|
|
};
|
|
|
|
export function partnerHasWechatBinding(
|
|
profile: PartnerProfile | PartnerSessionProfile | null | undefined,
|
|
): boolean {
|
|
return !!profile?.hasWechat;
|
|
}
|
|
|
|
/** 微信一键登录前:账号须已绑定 wxOpenId(本地缓存或手机号校验) */
|
|
export async function canPartnerUseWechatLogin(options: {
|
|
profile?: PartnerProfile | PartnerSessionProfile | null;
|
|
phone?: string;
|
|
}): Promise<boolean> {
|
|
const cached = options.profile ?? getPartnerProfile();
|
|
if (partnerHasWechatBinding(cached)) return true;
|
|
|
|
const normalized = options.phone?.trim() ?? '';
|
|
if (!/^1\d{10}$/.test(normalized)) return false;
|
|
|
|
try {
|
|
const res = await request<PartnerPhoneCheckResponse>('PARTNER_H5', '/partner/auth/phone/check', {
|
|
method: 'POST',
|
|
body: JSON.stringify({ phone: normalized }),
|
|
silent: true,
|
|
});
|
|
return !!res.hasWechat;
|
|
} catch {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/** 短信登录/发码前:校验手机号对应合伙人存在且未暂停 */
|
|
export async function checkPartnerPhoneForLogin(phone: string): Promise<PartnerPhoneCheckResponse> {
|
|
const normalized = phone.trim();
|
|
if (!/^1\d{10}$/.test(normalized)) {
|
|
throw new Error('请输入正确的手机号');
|
|
}
|
|
return request<PartnerPhoneCheckResponse>('PARTNER_H5', '/partner/auth/phone/check', {
|
|
method: 'POST',
|
|
body: JSON.stringify({ phone: normalized }),
|
|
silent: true,
|
|
});
|
|
}
|
|
|
|
export async function fetchClientConfig(): Promise<ClientRuntimeConfig> {
|
|
return request<ClientRuntimeConfig>('PARTNER_H5', '/common/client-config');
|
|
}
|
|
|
|
export async function fetchPartnerProfile(): Promise<PartnerProfile> {
|
|
return request<PartnerProfile>('PARTNER_H5', '/partner/me');
|
|
}
|
|
|
|
/** 微信内上传照片前需完成公众号 OAuth 绑定 */
|
|
export function needsWechatAuth(
|
|
profile: PartnerProfile | null,
|
|
config?: Pick<ClientRuntimeConfig, 'wxAuthorize'> | null,
|
|
): boolean {
|
|
if (config && !isWxAuthorizeEnabled(config)) return false;
|
|
return isWechatEnv() && !!profile && !profile.hasWechat;
|
|
}
|
|
|
|
export async function checkNeedsWechatAuth(profile: PartnerProfile | null): Promise<boolean> {
|
|
const config = await fetchClientConfig();
|
|
return needsWechatAuth(profile, config);
|
|
}
|
|
|
|
export function sessionFromWechatLogin(result: WechatLoginResult): PartnerSessionPayload | null {
|
|
if (!result.accessToken || !result.refreshToken) return null;
|
|
const partner = result.partner;
|
|
return {
|
|
accessToken: result.accessToken,
|
|
refreshToken: result.refreshToken,
|
|
partner: partner
|
|
? {
|
|
id: String(partner.id ?? ''),
|
|
name: String(partner.name ?? ''),
|
|
phone: String(partner.phone ?? ''),
|
|
companyName: partner.companyName ? String(partner.companyName) : undefined,
|
|
isPrimary: partner.isPrimary !== false,
|
|
hasWechat: true,
|
|
wxNickname: partner.wxNickname ? String(partner.wxNickname) : undefined,
|
|
wxAvatarUrl: partner.wxAvatarUrl ? String(partner.wxAvatarUrl) : undefined,
|
|
}
|
|
: undefined,
|
|
};
|
|
}
|
|
|
|
/** 处理微信登录/绑定结果,写入 7 天免登录 session */
|
|
export function handlePartnerWechatLoginResult(result: WechatLoginResult): PartnerSessionPayload | null {
|
|
const session = sessionFromWechatLogin(result);
|
|
if (!session) return null;
|
|
saveWechatSession(session);
|
|
return session;
|
|
}
|
|
|
|
export async function handlePartnerWechatCallback(): Promise<WechatLoginResult | null> {
|
|
if (!isWechatEnv()) return null;
|
|
const config = await fetchClientConfig();
|
|
if (!isWxAuthorizeEnabled(config)) return null;
|
|
return weixinSdk.handleOAuthCallback();
|
|
}
|
|
|
|
/**
|
|
* 微信一键登录(已绑定微信的合伙人账号免验证码)。
|
|
* 返回 session = 已登录;void = 已跳转授权页等待回调。
|
|
*/
|
|
export async function loginPartnerWithWechat(): Promise<PartnerSessionPayload | null | void> {
|
|
const config = await fetchClientConfig();
|
|
if (!isWxAuthorizeEnabled(config)) return null;
|
|
if (!isWechatEnv()) {
|
|
throw new Error(WECHAT_INAPP_REQUIRED_MSG);
|
|
}
|
|
const result = await weixinSdk.login();
|
|
if (result) return handlePartnerWechatLoginResult(result);
|
|
}
|
|
|
|
/** 短信登录成功后于微信内自动发起 OAuth,绑定 openId 便于后续免登 */
|
|
export async function bindPartnerWechatAfterSmsLogin(): Promise<void> {
|
|
const config = await fetchClientConfig();
|
|
if (!isWxAuthorizeEnabled(config)) return;
|
|
if (!isWechatEnv()) return;
|
|
await weixinSdk.login();
|
|
}
|
|
|
|
export async function authorizePartnerWechat(): Promise<WechatLoginResult | void> {
|
|
const config = await fetchClientConfig();
|
|
if (!isWxAuthorizeEnabled(config)) return;
|
|
if (!isWechatEnv()) {
|
|
throw new Error(WECHAT_INAPP_REQUIRED_MSG);
|
|
}
|
|
return weixinSdk.login();
|
|
}
|
|
|
|
/** OAuth 回跳统一处理(登录页 / 录店页等) */
|
|
export async function processPartnerWechatOAuthCallback(): Promise<PartnerSessionPayload | null> {
|
|
const result = await handlePartnerWechatCallback();
|
|
if (!result) return null;
|
|
return handlePartnerWechatLoginResult(result);
|
|
}
|
|
|
|
/** @deprecated 使用 handlePartnerWechatLoginResult */
|
|
export function savePartnerWechatAuth(result: WechatLoginResult): PartnerSessionPayload | null {
|
|
return handlePartnerWechatLoginResult(result);
|
|
}
|