微信生态开发:支付、登录与开放平台的坑

微信生态不是"一个 API",是一套规则:小程序、公众号、开放平台、支付,每个都有自己的凭证、接口和坑。这篇文章是六年微信开发(小程序/服务号/支付/开放平台)的踩坑总结。

一、三种身份凭证

凭证 用途 有效期
AppID + AppSecret 公众号/小程序的身份 永久
access_token 调用接口的令牌 2 小时,需缓存刷新
openid / unionid 用户身份 永久

核心概念:

o u 场 p n 景 e i : → → n o 用 i n 户 必 前 d i 在 须 提 : d 小 用 : 同 : 程 小 一 同 序 u 程 公 一 下 n 序 众 开 单 i 和 号 放 , o 公 / 平 公 n 众 小 台 众 i 号 程 账 号 d 都 序 号 要 绑 下 下 给 关 定 , , 他 联 到 同 所 发 ( 同 一 有 通 o 一 用 应 知 p 个 户 用 e 开 的 的 n 放 唯 统 i 平 一 一 d 台 用 账 I 户 对 号 D 不 ( I 上 不 D ) 同 ( 应 打 用 通 下 小 不 程 同 序 ) + 公 众 号 + A p p )

二、微信登录(自动授权)

流 程 : 前 → → 端 后 生 w 端 成 x 业 . c 务 l o o d t g e o i 2 k n S e ( e n ) s s → → i o 返 拿 n 回 前 c → 端 o d 拿 e o p e n i d + s e s s i o n _ k e y ( 小 程 序 )
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
// 后端(小程序登录)
func WxLogin(ctx, code string) (*LoginResult, error) {
    resp, _ := http.Get(fmt.Sprintf(
        "https://api.weixin.qq.com/sns/jscode2session"+
        "?appid=%s&secret=%s&js_code=%s&grant_type=authorization_code",
        appid, secret, code))
    // resp: {openid, session_key, unionid}
    user := findOrCreateByOpenid(resp.Openid)
    return issueToken(user), nil
}

坑:

✅ ✅ ✅ ✅ c c s 手 o o e 机 d d s 号 e e s 解 i 密 只 有 o 需 能 效 n 要 用 期 _ 一 k s 次 5 e e ( y s 前 分 s 端 钟 不 i 误 ( 要 o 重 过 下 n 发 期 发 _ 失 前 k → 效 端 e ) ( y 失 敏 ( 败 感 用 ) ) 后 即 换 )

三、微信支付

核心流程

用 → → → 户 下 返 用 后 单 回 户 端 支 支 验 → 付 付 签 参 后 数 → + 端 生 → 微 查 成 信 单 预 前 回 确 支 端 调 认 付 拉 单 起 n → ( 支 o 统 付 t 更 一 i 新 下 f 订 单 y 单 ) _ u r l ( 异 步 ! )

关键:支付回调(最容易错的)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
func PayNotify(w http.ResponseWriter, r *http.Request) {
    // 1. 读回调(XML/JSON)
    body := readBody(r)
    // 2. 验签(用 APIv3 密钥/证书)
    if !verifySignature(r, body) {
        w.WriteHeader(401); return   // 验签失败:不返回成功
    }
    // 3. 查单确认(别只信回调!回调可能重复/伪造)
    order := wx.QueryOrder(body.OutTradeNo)
    if order.Status != "SUCCESS" {
        w.WriteHeader(400); return
    }
    // 4. 幂等更新(回调可能来多次!)
    if !markOrderPaid(order.OutTradeNo) {   // DB 幂等
        w.WriteHeader(200); return          // 已处理过:返回成功(不重发)
    }
    // 5. 必须返回成功(否则微信会重试 24h!)
    w.WriteHeader(200)
}

回调的铁律:

✅ ✅ ✅ ✅ 验 只 幂 必 签 信 等 须 不 " 处 返 过 查 理 回 单 ( 成 → 结 重 功 果 复 ( 不 " 回 返 返 , 调 回 回 不 不 失 成 信 重 败 功 回 复 ( 调 发 = 微 内 货 信 容 ) 微 会 ( 信 重 回 重 试 调 试 ) 可 伪 2 造 4 / 重 小 复 时 ) = 重 复 回 调 风 暴 )

退款

退 款 比 1 2 3 4 支 . . . . 付 更 退 退 退 退 容 款 款 款 款 易 需 金 结 幂 踩 要 额 果 等 坑 证 不 也 : : 书 能 是 退 ( 超 异 款 A 过 步 单 P 可 通 号 I 退 知 唯 v 金 ( 一 3 额 退 ( ( 款 重 证 部 单 试 书 分 状 用 , 退 态 同 不 轮 一 是 v 询 退 密 s / 款 钥 回 单 ) 全 调 号 额 ) ) 退 )

四、开放平台

开 放 平 1 2 3 4 台 . . . . 的 坑 小 绑 第 多 : 程 定 三 主 序 后 方 体 / 不 平 : 公 可 台 不 众 轻 ( 同 号 易 代 主 绑 解 开 体 定 绑 发 的 开 ( ) 小 放 影 权 程 平 响 限 序 台 数 要 不 才 据 " 能 能 打 授 共 拿 通 权 用 ) 托 一 u 管 个 n " 开 i 放 o 平 n 台 i d

五、防重与安全

✅ ✅ ✅ ✅ ✅ a 支 敏 金 回 c 付 感 额 调 c 回 接 单 来 e 调 口 位 源 s 幂 校 : 校 s 等 验 分 验 _ ( ( ( ( t 订 o 整 验 o 单 p 型 k 状 e , I e 态 n 别 P n 机 i 用 / : d 浮 验 全 待 点 签 局 支 与 ! 名 缓 付 ) , 存 → t 防 已 o 伪 + 支 k 造 付 e 回 刷 → n 调 新 已 ) 锁 发 用 ( 货 户 多 ) 一 实 致 例 ) 并 发 刷 会 互 踢 ! )

六、踩坑记录

  1. access_token 并发刷新互踢:多个实例同时刷新 → 旧的失效,新的也失效 → 加分布式锁 + 本地缓存
  2. 金额用浮点:0.1 + 0.2 = 0.30000000000000004 → 订单金额错 → 一律用"分"(整数)
  3. 回调重复触发:返回非 200 → 微信重试 24h → 幂等处理 + 必须返回成功
  4. unionid 拿不到:小程序没绑开放平台 → 调接口没 unionid → 先绑再开发

总结

微信生态的核心认知:

  1. 三种凭证 + 两种用户 ID:openid 应用内唯一,unionid 跨应用打通
  2. 支付回调四铁律:验签、查单、幂等、必返成功
  3. 金额用分:整数运算,浮点是事故源
  4. access_token 全局管理:缓存 + 刷新锁,防互踢

微信生态的复杂度不在"接口多",在"状态机多":登录态、支付态、退款态,每个状态转换都有规则和坑。把状态机画清楚,微信开发就成功了一半。