手机号获取:微信的能力边界 + 我们的挂点台账

2026-10-21 波波。写这份的起因:义父问「微信登录不是自动获取微信号和绑定手机吗?」
一路查下来发现两件事——① 登录那次只能拿到 openid/unionid,拿不到手机号(微信限制,不是我们写漏);
② 真正漏的是 unionid(1043 个用户该列全是空)。
记下来是为了以后不再有人(包括我)去赌「点一下聊天就静默拿到手机号」。

一、微信到底给什么(截至 2026-10-21)

途径能拿到前提代价
jscode2session(登录)openid、session_key、unionid(若绑开放平台)code 一次性免费、用户无感
getPhoneNumber真实手机号用户主动点击授权 + 企业主体 + 小程序已认证按次计费 0.03 元
getUserInfo / getUserProfile昵称头像2022-02 起已回收—
客服会话什么都不给(会话内容归客服系统)——

结论:

对同一个微信用户给同一个 unionid ⇒ 能免费把「看慢直播的人」和「进小程序的人」认成同一个人。

前提:小程序必须已绑定微信开放平台(未绑定 ⇒ 返回为空,登录不受影响)。


二、平台硬约束(决定了所有做法)

  1. 一个 <button> 的 open-type 只能有一个值 ⇒ getPhoneNumber 与 contact 互斥,

不存在「一个按钮既进客服会话又拿到手机号」。

  1. open-type="contact" 由微信立即拉起客服会话,前端拦不住(catchtap 也拦不住),

所以「聊天之前先弹授权」这条路是死的。

  1. js_code 是一次性凭证 ⇒ unionid 必须在同一次 code2Session 里取回,

不能事后「再调一次补上」(第二次必失败)。

  1. getPhoneNumber 是付费接口 ⇒ 只在用户主动点的地方触发,不做冷启动轰炸。

三、我们的挂点台账

A. 手机号(getPhoneNumber,需点击)

挂点位置状态
下单联系人表单components/foundation/f-contact-form(一处改,item-detail / product-detail / hotel-booking 三页全生效)✅ 已绑过的号自动回填;未绑给「用微信手机号填入」
联系条常驻入口components/foundation/f-concierge-bar(bar + inline 两种形态)✅ 未绑号时显示「留个号,管家回你」
商品详情咨询旁pages/product-detail✅
「我的」页pages/profile✅(既有)
全部客服(聊天)入口utils/phoneAuth.promptAfterChat()✅ 15/18 个 contact 按钮已挂(见下)

聊天入口的做法(因为约束 2,只能这样):在 bindtap(必先于 open-type 行为触发,时序确定)

里把授权浮层挂上、延时 600 ms 弹出 ⇒ 用户关掉客服会话回到页面时浮层正等着他。

刻意不用 bindcontact:它在不同微信版本触发时机不一致,赌它等于埋一个静默失效的坑。

冷却 7 天一次;已绑号 / 页面没挂浮层 ⇒ 静默放过。

覆盖账(全站 18 个 open-type="contact" 按钮 / 10 个 wxml):

hotel-booking、local×6(含 3 个 catchtap 的 onAskStop)、product-detail

B. unionid(登录时顺手取,免费无感)

backend/app/routers/auth.py:_resolve_openid → _resolve_wechat_identity,返回 (openid, unionid);

新用户 INSERT 带上;存量用户只补空值、绝不覆盖(下次打开小程序自动补上)。

回归测试 backend/tests/test_login_unionid.py(7 条)。


四、还没做 / 待办