一、引言
共享充电宝这个业务,几乎每一个关键环节都离不开微信生态:
- 用户要用微信小程序扫码
- 租借前需要手机号授权完成登录
- 租借时要用微信支付分免押金
- 归还后通过支付分自动扣款
- 异常状态需要实时推送通知运营
如果你的平台不跟微信深度绑定,基本玩不转。这一篇就把我们接入微信生态的几个核心模块从头到尾拆解一遍。
二、微信小程序登录:三步搞定
小程序登录流程看起来简单,但涉及三个微信接口的串联调用,外加我们自己系统的用户注册/登录逻辑。
整体流程:
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"
}
}每个配置项都有明确的用途,部署时只需要改这一个节点。
七、小结
微信生态的接入是充电宝平台必不可少的一环。几个关键经验:
- 注册登录一体化——减少用户操作步骤,扫码即用
- 支付分免押金是用户转化的关键,必须优先接入
- 回调验签不能省——微信支付 V3 的签名验证机制虽然麻烦,但安全不能妥协
- SignalR 替代轮询——实时推送比定时拉取体验好太多
下一篇会讲运营中台与商户后台的权限体系,看我们怎么用一套 RBAC 支撑两套前端、四种角色、N 个数据权限维度。
有疑问或者想讨论的点,留言区见。