×

一台轻量服务器 + 一个微信号,搭一个能收钱的微信公众号应用要多少钱?

hqy hqy 发表于2026-03-23 22:05:29 浏览4 评论0

抢沙发发表评论

事情是这样的,这两天我给一个很好的朋友做了一个微信公众号应用,带支付和管理后台,全部可以在微信内部闭环。我就在想,身在这个时代,也许是全民创业时代,但是,我总觉得,能收款才是闭环,不收款,你的商业逻辑也就没法闭环。所以这篇文章手把手带你走通:从一台空服务器到微信支付到账的全流程。这个过程在AI 的帮助下,我仅仅花了 1 天时间搞定。如下图,跑通的界面。

图片


首先,我们来先算一笔账,这个应用一年的初始成本是多少

很多人觉得做一个能收钱的线上应用门槛很高,其实拆开算一下:

项目
费用
备注
轻量云服务器
~70元/月
2核2G足矣,3个月约200
域名
33元/年
部分渠道甚至免费
SSL证书
0元
Let's Encrypt 免费申请
微信公众号认证
300元/年
需要企业主体
微信商户号
0元
企业主体免费开通
合计约600元/年
一顿好点的火锅钱


600块一年,就能跑起一个有登录、有支付、有管理后台的完整应用。

技术选型:我用 Next.js ,但是为啥我选这套呢?

Next.js(前后端一体) + SQLite(零运维数据库) + 微信生态(登录+支付)

选型逻辑很简单:一个人能维护

  • • Next.js:一个项目搞定前端页面、后端API、中间件鉴权,不用分别部署前后端
  • • SQLite:一个文件就是整个数据库,不用装 MySQL,不用管连接池,备份就是复制文件,当然,如果你觉得你又个几万用户,那么选 MySQL 没错。
  • • Prisma:ORM 层,写 TypeScript 就能操作数据库,有类型提示不容易写错
  • • 微信 OAuth:用户在微信里打开就自动登录,不用自己做注册登录体系
  • • 微信支付 JSAPI:用户在微信内直接拉起支付,体验最顺滑

一台服务器跑一个 Node 进程,PM2 守护,Nginx 反代,够了。

整体架构

用户微信 ──▶ Nginx(:443) ──▶ Next.js(:3000)
                                  │
                    ┌──────────────┼──────────────┐
                    ▼              ▼              ▼
              微信OAuth       业务逻辑        微信支付API
              (拿openid)    (Prisma+SQLite)   (下单/回调)

整个应用分三层:

  1. 1. 用户层:微信内 H5 页面,OAuth 静默登录拿 openid
  2. 2. 业务层:Next.js 处理核心逻辑,Prisma 读写 SQLite
  3. 3. 支付层:调微信支付 JSAPI 下单,处理异步回调

下面逐个拆解。

第一关:微信 OAuth 登录

微信生态内的用户身份标识是 openid——每个用户在你的公众号下有唯一的 openid。拿到它,就知道"谁在用你的应用"。

流程

用户访问你的页面
    │
    ▼
中间件检查 session 里有没有 openid
    │
    ├── 有 → 放行
    │
    └── 没有 → 跳转微信 OAuth 授权页
                    │
                    ▼
              用户同意授权(静默,无感知)
                    │
                    ▼
              微信回调你的服务,带上 code
                    │
                    ▼
              用 code 换 openid,写入 session
                    │
                    ▼
              跳回原页面

中间件实现

关键:把鉴权逻辑放在 Next.js 中间件里,所有需要登录的页面自动保护。

// middleware.ts
exportasyncfunctionmiddleware(requestNextRequest) {
const { pathname } = request.nextUrl;
const session = await getIronSession<SessionData>(request, response, sessionOptions);

// 管理后台:必须登录 + 管理员身份
if (pathname.startsWith("/admin")) {
    if (!session.openid) {
      returnredirect("/api/auth/wechat?redirect=" + pathname);
    }
    const admins = process.env.ADMIN_OPENIDS?.split(",") || [];
    if (!admins.includes(session.openid)) {
      returnredirect("/");
    }
  }

// C端页面:微信内要求登录,非微信放行
if (pathname.startsWith("/wechat")) {
    const ua = request.headers.get("user-agent") || "";
    const isWeChat = ua.includes("MicroMessenger");
    if (!session.openid && isWeChat) {
      returnredirect("/api/auth/wechat?redirect=" + pathname);
    }
  }
}

这里有个细节:非微信浏览器直接放行。因为 OAuth 只在微信内生效,PC 或手机浏览器访问时跳 OAuth 会报错。让非微信用户也能看(免费或体验模式),比硬拦着体验好得多。

OAuth 路由

// app/api/auth/wechat/route.ts
exportasyncfunctionGET(requestNextRequest) {
const code = searchParams.get("code");

if (!code) {
    // 第一步:跳微信授权页
    const oauthUrl = `https://open.weixin.qq.com/connect/oauth2/authorize?`
      + `appid=${APPID}&redirect_uri=${encodeURIComponent(redirectUri)}`
      + `&response_type=code&scope=snsapi_base#wechat_redirect`;
    returnNextResponse.redirect(oauthUrl);
  }

// 第二步:用 code 换 openid
const res = awaitfetch(
    `https://api.weixin.qq.com/sns/oauth2/access_token?`
    + `appid=${APPID}&secret=${SECRET}&code=${code}&grant_type=authorization_code`
  );
const data = await res.json();
  session.openid = data.openid;
await session.save();
returnredirect(originalPath);
}

scope=snsapi_base 是静默授权,用户无感知,不需要点"同意"。只能拿到 openid,拿不到昵称头像,但对大多数应用够用了。

第二关:微信支付接入

这是全文最核心的部分,也是第一接入最麻烦的地方,当然了,也是坑最多的地方。

前置准备

  1. 1. 企业认证的公众号(服务号),配好 JS 接口安全域名和网页授权域名,注意,域名需要先备案成功
  2. 2. 微信商户号,在 pay.weixin.qq.com 申请,需要企业。
  3. 3. 关联 AppID 和商户号:商户平台 → 产品中心 → AppID账号管理 → 关联你的公众号 AppID,自己操作就好,不需要审核。
  4. 4. API 证书:商户平台 → 账户中心 → API安全 → 申请 API 证书,下载 apiclient_key.pem 和 apiclient_cert.pem
  5. 5. 设置 API V3 密钥:同一页面设置 32 位密钥,记下来

图片

图片

注意,这个地方先申请最底下的 V3密钥,然后在申请商户 API 证书,V2,那个不太需要了,那个是过时的 API ,不用理他。申请证书的需要一个工具,流程会引导你下载。几分钟整完,也不是很难。



图片



支付时序

这是完整的一次支付流程:

 前端                    你的服务器                 微信支付API
  │                        │                        │
  │  1. 点击支付            │                        │
  │───────────────────────▶│                        │
  │                        │  2. 创建预支付订单       │
  │                        │───────────────────────▶│
  │                        │                        │
  │                        │  3. 返回 prepay_id      │
  │                        │◀───────────────────────│
  │  4. 返回支付参数        │                        │
  │◀───────────────────────│                        │
  │                        │                        │
  │  5. wx.requestPayment  │                        │
  │  (拉起微信支付弹窗)     │                        │
  │─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─▶│
  │                        │                        │
  │  6. 用户完成支付        │                        │
  │◀ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ │
  │                        │                        │
  │                        │  7. 异步回调通知(加密)   │
  │                        │◀───────────────────────│
  │                        │                        │
  │                        │  8. 解密,更新订单状态    │
  │                        │                        │
  │  9. 轮询支付状态        │                        │
  │───────────────────────▶│                        │
  │  10. 返回 PAID          │                        │
  │◀───────────────────────│                        │
  │                        │                        │
  │  11. 跳转结果页 ✓       │                        │

注意第 7 步:微信的支付结果不是同步返回的。用户付完钱,微信会异步 POST 到你配置的回调地址。前端靠轮询来确认支付状态。

后端:创建预支付订单

// lib/wxpay.ts — 初始化支付客户端
importWxPayfrom"wechatpay-node-v3";

const wxpay = newWxPay({
appid: process.env.WECHAT_APPID,
mchid: process.env.WX_MCH_ID,
publicKey: fs.readFileSync("/path/to/apiclient_cert.pem"),   // 公钥证书
privateKey: fs.readFileSync("/path/to/apiclient_key.pem"),   // 私钥
});

这里是第一个大坑publicKey 要传 公钥证书apiclient_cert.pem),其实很好理解,证书有点网站的 HTTPS 证书,就是给人家去认证的,但是不小心传错了,排查起来会比较麻烦,无厘头。如果把私钥同时传给两个字段,会报:

Error: Could not convert certificate from PEM: invalid type

这个错误信息完全看不出是哪个文件传错了,都是 pem,很容易在这卡几分钟。

// app/api/pay/create/route.ts
exportasyncfunctionPOST(requestNextRequest) {
const { orderId } = await request.json();
const order = await prisma.order.findUnique({ where: { id: orderId } });

// 调用微信 JSAPI 下单
const result = await wxpay.transactions_jsapi({
    description"你的商品描述",
    out_trade_no: order.id,
    notify_url"https://yourdomain.com/api/pay/notify",
    amount: { total: order.amountcurrency"CNY" },
    payer: { openid: order.openid },
  });

// wechatpay-node-v3 直接返回前端需要的支付参数
// { appId, timeStamp, nonceStr, package, signType, paySign }
returnNextResponse.json({ payParams: result.data });
}

第二个坑wechatpay-node-v3 的 transactions_jsapi 返回的不是原始 prepay_id,而是已经签好名的完整 JSAPI 参数。如果你还自己拿 prepay_id 去拼签名,反而会报参数错误。直接 result.data 透传给前端就行。

第三个坑APPID_MCHID_NOT_MATCH。AppID 和商户号必须在微信商户平台上绑定。去 pay.weixin.qq.com → 产品中心 → AppID账号管理,确认关联关系。

前端:拉起支付

// 前端调用 wx.requestPayment
function onPay(payParams) {
  wx.chooseWXPay({
    timestamp: payParams.timeStamp,
    nonceStr: payParams.nonceStr,
    package: payParams.package,
    signType: payParams.signType,
    paySign: payParams.paySign,
    success() => { /* 开始轮询支付状态 */ },
    fail() => { /* 用户取消或失败 */ },
  });
}

后端:处理支付回调

第四个坑,也是最隐蔽的一个:微信 V3 的回调通知是 AES-256-GCM 加密 的。

你收到的回调长这样:

{
  "event_type":"TRANSACTION.SUCCESS",
"resource":{
    "algorithm":"AEAD_AES_256_GCM",
    "ciphertext":"a01ruYbf7C7ItSb9G6zYtH...(一大串Base64)",
    "nonce":"RZEIMd1L1xBM",
    "associated_data":"transaction"
}
}

看到 ciphertext 了吗?里面才是真正的支付结果(包含 out_trade_no、金额等)。很多教程没提这一步,如果你不解密直接去读 out_trade_no,永远是 undefined

解密代码:

import crypto from"crypto";

functiondecryptResource(ciphertextstringnoncestringassociatedDatastring) {
const apiV3Key = process.env.WX_API_V3_KEY;  // 32位密钥
const buf = Buffer.from(ciphertext, "base64");

// 密文最后16字节是 authTag
const authTag = buf.subarray(buf.length - 16);
const encrypted = buf.subarray(0, buf.length - 16);

const decipher = crypto.createDecipheriv("aes-256-gcm", apiV3Key, nonce);
  decipher.setAuthTag(authTag);
  decipher.setAAD(Buffer.from(associatedData));

returnBuffer.concat([decipher.update(encrypted), decipher.final()]).toString("utf8");
}

解密后拿到明文:

{
  "out_trade_no": "你的订单号",
  "trade_state": "SUCCESS",
  "amount": { "total": 1 }
}

然后更新订单状态为 PAID,完事。

前端:轮询确认

// 用户付完钱后轮询
const timer = setInterval(async () => {
  const res = await fetch(`/api/pay/status?orderId=${orderId}`);
  const data = await res.json();
  if (data.status === "PAID") {
    clearInterval(timer);
    router.push(`/paid/${orderId}`);
  }
}, 2000);

为什么不直接信任前端的 success 回调?因为那个回调只表示"用户操作完成了",不等于"钱真的到账了"。以服务端回调结果为准,这是支付系统的铁律。

图片

第三关:管理后台

收了钱,还得能看到。管理后台本质就是:用 openid 鉴权 + 一套 CRUD 页面

// lib/admin-auth.ts
exportasyncfunctionisAdmin() {
const session = awaitgetSession();
const envAdmins = process.env.ADMIN_OPENIDS?.split(",") || [];
// 先查环境变量(快),再查数据库(灵活)
const isEnvAdmin = envAdmins.includes(session.openid);
const isDbAdmin = await prisma.adminUser.findUnique({ where: { openid: session.openid } });
return isEnvAdmin || !!isDbAdmin;
}

管理员身份两级校验:环境变量写死的(你自己),这就初始蛋,蛋生鸡,哈哈。+ 数据库动态添加的(你的合伙人)。

后台功能不用复杂,核心就几个:

  • • 订单列表:看谁付了钱、什么时候付的、多少钱
  • • 数据看板:今日/本周/本月收入
  • • 内容管理:编辑你的商品/服务内容
  • • 系统设置:调价格、改公告
  • • 管理员管理:加人、删人

用 shadcn/ui 搭界面,差不多顺利点1小时能撸完。

图片
图片


部署:一把梭

# 服务器上
git pull
npm install
npx prisma db push
npm run build

# PM2 守护进程
pm2 start ecosystem.config.js
pm2 save

Nginx 配置:

server {
    listen 443 ssl;
    server_name yourdomain.com;

    ssl_certificate     /path/to/fullchain.pem;
    ssl_certificate_key /path/to/privkey.pem;

    location / {
        proxy_pass http://127.0.0.1:3000;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
    }
}

写在最后

全民创业的时代,技术不该是门槛。一台轻量服务器、一个认证公众号、几百行代码,就能搭起一个从登录到支付到管理的完整闭环。

真正的门槛从来不是技术,是你愿不愿意动手把第一个版本跑起来。


打赏

本文链接:https://kinber.cn/post/6329.html 转载需授权!

分享到:


推荐本站淘宝优惠价购买喜欢的宝贝:

image.png

 您阅读本篇文章共花了: 

群贤毕至

访客