by tcdos 2026-06-27

不止扫码 — 微信生态深度融合(登录 + 支付 + 消息)

一、引言

共享充电宝这个业务,几乎每一个关键环节都离不开微信生态:

  • 用户要用微信小程序扫码
  • 租借前需要手机号授权完成登录
  • 租借时要用微信支付分免押金
  • 归还后通过支付分自动扣款
  • 异常状态需要实时推送通知运营

如果你的平台不跟微信深度绑定,基本玩不转。这一篇就把我们接入微信生态的几个核心模块从头到尾拆解一遍。

二、微信小程序登录:三步搞定

小程序登录流程看起来简单,但涉及三个微信接口的串联调用,外加我们自己系统的用户注册/登录逻辑。

整体流程:

wx.login() → code → getOpenid
                        ↓
getPhoneNumber() → code → getPhone
                        ↓
                   wxLogin(openid, phone) → JWT Token
                        ↓
                   存储到 Storage

小程序端的实现:

async wxLogin(e) {
    const code = e.detail.code;  // 手机号授权返回的 code

    // 第一步:wx.login 获取临时 code
    const res1 = await login();

    // 第二步:用临时 code 换 openid
    const res2 = await api.getWxUserOpenid(res1.code);

    // 第三步:用手机号授权 code 换手机号
    const res3 = await api.getWxUserPhone(code);

    // 第四步:用 openid + 手机号登录/注册
    const res4 = await api.wxLogin({ phone, openid });

    // 保存 Token 和用户信息
    wx.setStorageSync("wx-token", res4.data);
    wx.setStorageSync("wx-userinfo", { openid, phone, userId, ... });
}

后端 WxMiniHelper 负责跟微信 API 通信。获取 openid 的代码极其简单,就是一次 HTTP GET:

public async Task<string> GetWxOpenid(string code)
{
    string url = $"https://api.weixin.qq.com/sns/jscode2session" +
        $"?appid={appid}&secret={secret}&js_code={code}&grant_type=authorization_code";
    string res = await httpClientFactory.HttpSendAsync(HttpMethod.Get, url);

    JObject result = JObject.Parse(res);
    return result["openid"]?.ToString();
}

获取手机号稍微绕一点,需要先拿 access_token

public async Task<string> GetWxUserPhoneNumber(string code)
{
    string accessToken = await GetWxAccessToken();  // 先取 token
    string url = $"https://api.weixin.qq.com/wxa/business/getuserphonenumber" +
        $"?access_token={accessToken}";

    string res = await httpClientFactory.HttpSendAsync(HttpMethod.Post, url, new { code });
    JObject result = JObject.Parse(res);
    return result["phone_info"]["purePhoneNumber"]?.ToString();
}

后端的登录接口包含一个关键设计——注册登录一体化

public async Task<ResultModel<SysUserEntity>> WxLoginAndRegisterAsync(WxLoginDto model)
{
    var user = await currentRepository.FirstOrDefaultAsync(u => u.Phone == phone);

    if (user == null)
    {
        // 首次访问:自动注册
        SysUserEntity entity = new SysUserEntity()
        {
            UserId = ZnResponse.GuId(),
            UserName = $"#{phone[7..]}",       // 默认昵称:手机号后4位
            Phone = phone,
            WxOpenid = openid,
            SecurityLevel = 1,                  // 普通用户
            IsEnabled = 1,
            CreatedDate = DateTime.Now
        };
        await currentRepository.AddAsync(entity);
        return new ResultModel<SysUserEntity>(true, "登录成功", entity);
    }
    else
    {
        // 已注册:更新 openid 后直接登录
        if (string.IsNullOrEmpty(user.WxOpenid) || user.WxOpenid != openid)
        {
            user.WxOpenid = openid;
            await currentRepository.UpdateAsync(user, true);
        }
        return new ResultModel<SysUserEntity>(true, "登录成功", user);
    }
}

设计要点: 用户第一次扫码就会自动注册,不需要额外填手机号、设密码。默认昵称用手机号后四位,安全级别设为普通用户。下次再用直接登录。

登录成功后颁发 JWT,有效期为 180 天:

var jwt = JWTExtension.CreateJWT(
    result.Data.UserId,
    result.Data.UserName,
    result.Data.SecurityLevel.ObjToInt(1),
    259200  // 180天
);
return new ResultModel<JWTDto>(result.Status, result.Message, jwt);

三、微信支付 V3:JSAPI 下单 + 回调签名验证

支付走的是微信 JSAPI 模式,用户在微信小程序内调起支付,流程如下:

后端:调用微信 API 下单 → 获取 prepay_id
       ↓
前端:用 prepay_id 生成签名 → 调起 wx.requestPayment
       ↓
微信:支付成功后异步回调后端通知地址
       ↓
后端:验证签名 → 解密回调数据 → 更新订单状态

下单接口:

public async Task<ResultModel<WxMiniPayModel>> GenerateOrder(PayDto model)
{
    // 查订单/欠费记录
    var orderEntity = await orderRepository.FirstOrDefaultAsync(
        x => x.OrderId == id && x.Amount > 0);

    // 调用微信下单
    var wxPayRes = await wxPayHelper.CreateOrder(
        openid, sn, desc, amount, typeId.ToString());

    if (wxPayRes.status == true)
    {
        // 构造小程序调起支付所需的参数
        var packageStr = $"prepay_id={wxPayRes.prepay_id}";
        var timeStamp = DateTimeOffset.Now.ToUnixTimeSeconds();
        var nonceStr = Path.GetRandomFileName();
        var message = $"{appid}\n{timeStamp}\n{nonceStr}\n{packageStr}\n";
        var signature = WxPayUtils.RSASignData(message, mchid);

        return new ResultModel<WxMiniPayModel>(new WxMiniPayModel
        {
            timeStamp = timeStamp.ToString(),
            nonceStr = nonceStr,
            packageStr = packageStr,
            paySign = signature
        });
    }
}

回调通知解析:

微信支付的回调通知用 AES-GCM 加密,需要先验签再解密:

[Route("callback")]
[HttpPost]
public async Task<WxPayNotifyReturnModel> PayMiniNotify()
{
    // 1. 读取原始报文
    string strNotice = Encoding.UTF8.GetString(buffer);

    // 2. 验签(用微信平台证书)
    var verify = SignVerify(strNotice);

    // 3. 解密(AES-GCM)
    string apiV3Key = Appsettings.Get(["Connect", "WX", "PayV3Key"]);
    var decryptStr = WxPayUtils.AesGcmDecrypt(
        associatedData, nonce, ciphertext, apiV3Key);

    // 4. 处理业务
    switch (event_type)
    {
        case "TRANSACTION.SUCCESS":
            // 更新订单为已支付
            break;
        case "REFUND.SUCCESS":
            // 更新退款状态
            break;
    }
}

这里的签名验证是重点。微信支付 V3 要求用平台证书公钥验签,而不能像 V2 那样只用商户密钥。我们封装了一个 WxPayUtils.SignVerify 方法来处理,确保回调安全。

四、微信支付分:免押金租借的核心

这是整个项目最关键的第三方集成。共享充电宝如果不支持免押金租借,用户转化率会大打折扣。

微信支付分(服务端)的模式是:先享后付——用户先使用,归还后再扣款。如果用户不归还,微信会垫付资金给商户。

整体流程:

创建订单时 → 同时创建微信支付分订单(含风险押金)
用户确认支付分 → 弹宝
归还设备 → 计算金额 → 完结支付分订单
微信扣款 → 回调通知

创建支付分订单:

public async Task<WxServicePayOrderResModel> CreateOrderAsync(
    string openid, string out_order_no, string service_introduction,
    List<PostPayment> post_payments, string attach = "")
{
    var body = new WxServicePayOrderModel
    {
        appid = appid,
        service_id = serviceid,           // 微信支付分服务 ID
        openid = openid,
        service_introduction = service_introduction,
        out_order_no = out_order_no,       // 商户订单号
        notify_url = callbackUrl,
        post_payments = post_payments,
        time_range = new TimeRange()
        {
            start_time = "OnAccept"        // 用户确认即开始
        },
        risk_fund = new RiskFund()
        {
            name = "DEPOSIT",
            amount = riskFundAmount,       // 风险押金(99元)
        },
    };

    var client = httpClientFactory.CreateClient("WxPayHttpClient");
    var bodyJson = new StringContent(body.ToJson(), Encoding.UTF8, "application/json");
    var res = await client.PostAsync(url, bodyJson);
    var strRes = await res.Content.ReadAsStringAsync();
    return strRes.ToObject<WxServicePayOrderResModel>();
}

这里有个关键点——风险押金。虽然用户不需要实际支付押金,但微信支付分要求商户设置一个风险金额,当用户违约时用于赔付。我们设置的是 99 元,覆盖大多数订单金额。

确认支付分:

订单创建后,小程序端调用 wx.openBusinessView 拉起微信支付分确认页面:

wx.openBusinessView({
    businessType: 'wxpayScoreUse',
    extraData: item2,
    fail() {
        that.cancel();  // 用户未确认,自动取消订单
    },
});

用户在微信内确认后,后端轮询等待确认结果:

// 轮询确认状态
for (int i = 1; i <= 10; i++)
{
    await Task.Delay(800);
    wxOrder = await wxServicePayHelper.QueryOrderByQueryidAsync(query_id);

    if (wxOrder.state == "DOING" &&
        wxOrder.state_description == "USER_CONFIRM")
    {
        result = true;  // 用户已确认
        break;
    }
}

完结支付分订单:

用户归还设备后,后端自动发起完结:

public async Task<WxServicePayOrderResModel> CompleteOrderAsync(
    string out_order_no, int total_amount,
    List<PostPayment> post_payments, DateTime end_time)
{
    var body = new WxServicePayCompleteModel
    {
        appid = appid,
        service_id = serviceid,
        total_amount = total_amount,
        time_range = new TimeRange()
        {
            end_time = end_time.ToString("yyyyMMddHHmmss")
        },
        post_payments = post_payments,
    };

    var client = httpClientFactory.CreateClient("WxPayHttpClient");
    var bodyJson = new StringContent(body.ToJson(), Encoding.UTF8, "application/json");
    var res = await client.PostAsync(url, bodyJson);
    var strRes = await res.Content.ReadAsStringAsync();
    return strRes.ToObject<WxServicePayOrderResModel>();
}

完结时传入 total_amount(最终金额)和 post_payments(费用明细),微信展示给用户看并据此扣款。

支付分还支持催收扣款修改金额退款等操作,我们在退款流程、后台手动完结等场景中都用到了。

支付的 HTTP 客户端配置:

微信支付 V3 要求每个请求都必须用商户证书签名,我们在 IHttpClientFactory 中注册了一个专用客户端:

services.AddHttpClient("WxPayHttpClient")
    .ConfigurePrimaryHttpMessageHandler(() =>
    {
        var certPath = Path.Combine(env.WebRootPath, "Certs/Wechat/apiclient_cert.p12");
        var handler = new HttpClientHandler();
        handler.ClientCertificates.Add(new X509Certificate2(certPath, mchid));
        return handler;
    });

所有微信支付的请求都走这个客户端,自动携带商户证书,不需要在每个方法里重复处理签名。

五、SignalR 实时推送:从轮询到推送

运营后台需要实时掌握设备异常和订单状态变更。如果全部用轮询,体验差不说,对服务器也是负担。我们用 SignalR 做了实时推送。

服务端配置:

Program.cs 中注册 SignalR:

builder.Services.AddSignalR();

// ...

app.UseEndpoints(endpoints =>
{
    endpoints.MapHub<MessageHub>("/messageHub");
    endpoints.MapControllers();
});

MessageHub 核心逻辑:

public class MessageHub : Hub<IMessageClient>
{
    public async Task<ResultModel> SendMessage(MessageDto message)
    {
        // 1. 消息入库
        efDbContext.Add(new SysMessageEntity { ... });
        await efDbContext.SaveChangesAsync();

        // 2. 接收人在线则推送
        SignalRClientEntity client = efDbContext.SignalRClient
            .FirstOrDefault(x => x.UserId == message.ReceiveUserId);

        if (client != null)
        {
            if (message.CategoryId == (int)MessageCategory.Broadcast)
            {
                await Clients.All.ReceiveMessage(...);
            }
            else
            {
                await Clients.Client(client.ConnectionId).ReceiveMessage(...);
            }
        }
    }
}

支持两种推送模式:

  • 点对点:推送给指定用户
  • 广播:推送给所有在线用户

连接/断开时记录在线状态:

public override Task OnConnectedAsync()
{
    string connId = Context.ConnectionId;
    // 记录客户端连接
    using var efDbContext = ServiceLocator...
    efDbContext.SignalRClient.Add(new SignalRClientEntity
    {
        ConnectionId = connId,
        UserId = userId,
        IsOnline = 1,
        CreatedDate = DateTime.Now
    });
}

public override Task OnDisconnectedAsync(Exception exception)
{
    // 标记离线
    var client = efDbContext.SignalRClient
        .FirstOrDefault(x => x.ConnectionId == connId);
    client.IsOnline = 0;
}

SignalR 还携带了 JWT 令牌校验。在 AddSecurity 配置中注册了 SignalR 的令牌处理:

options.Events = new JwtBearerEvents()
{
    OnMessageReceived = context =>
    {
        if (context.Request.Query.TryGetValue("access_token", out var accessToken)
            && context.HttpContext.Request.Path.StartsWithSegments("/messageHub"))
        {
            context.Token = accessToken;
        }
        return Task.CompletedTask;
    }
};

这样 SignalR 连接也会经过 JWT 认证,确保只有登录用户才能连接。

六、微信生态配置一览

所有微信相关的配置集中在 appsettings.json 的一个节点下:

"Connect": {
    "WX": {
        "AppId": "******",
        "Secret": "******",
        "Mchid": "******",
        "PayV3Key": "******",
        "SerialNo": "******",
        "PayCallbackUrl": "https://tcdos.com/api/wx/pay/callback",
        "RefundCallbackUrl": "https://tcdos.com/api/wx/pay/refund/callback",
        "AppCertPath": "/Certs/Wechat/apiclient_cert.p12",
        "Serviceid": "******",
        "ServiceRiskFundAmount": 9900,
        "ServicePayCallbackUrl": "https://tcdos.com/api/wx/pay/service/callback"
    }
}

每个配置项都有明确的用途,部署时只需要改这一个节点。

七、小结

微信生态的接入是充电宝平台必不可少的一环。几个关键经验:

  1. 注册登录一体化——减少用户操作步骤,扫码即用
  2. 支付分免押金是用户转化的关键,必须优先接入
  3. 回调验签不能省——微信支付 V3 的签名验证机制虽然麻烦,但安全不能妥协
  4. SignalR 替代轮询——实时推送比定时拉取体验好太多

下一篇会讲运营中台与商户后台的权限体系,看我们怎么用一套 RBAC 支撑两套前端、四种角色、N 个数据权限维度。

有疑问或者想讨论的点,留言区见。

天草设计公众号

欢迎关注 天草设计 公众号

好的设计被看见,好的开发被实现。

围观:11| 收获点赞:0

评论 0

暂无评论,来说点什么吧

?
内容目录
快捷操作
点赞 评论 返回顶部