☰
微信小程序商店完整可运行代码:登录态穿透到支付闭环
2026/10/15 7:08:26 网站建设 项目流程

简介:这是一份面向微信小程序开发者的学习型实战代码资源,聚焦小程序商店类应用的全栈实现,适用于希望掌握小程序生态内应用分发平台开发逻辑的中初级开发者。资源包含57个文件,涵盖9个JS业务逻辑文件、8个WXML页面结构文件、8个WXSS样式文件、9个JSON配置文件及17张PNG图标资源,整体压缩包仅131KB,轻量易读,目录结构清晰呈现pages(页面)、utils(工具函数)、images(静态资源)等典型小程序模块。已有4040人学习下载,说明其在小程序进阶开发场景中具备较强参考价值。读者可直接基于该代码理解小程序商店的核心架构——包括分类展示、搜索跳转、小程序码生成与扫码唤起等关键流程,并快速复现用户界面与后端数据对接逻辑,是深入理解微信开放能力(如小程序码API、登录授权、数据缓存策略)与工程化实践的优质入门范例。

1. 微信小程序商店完整代码:不是模板套壳,而是从登录态穿透到商品瀑布流的全链路可运行工程

“微信小程序商店完整代码”这个标题在开发者社区里高频出现,但多数人点进去发现只是个带 mock 数据的首页 UI 框架,连用户登录都走不通,更别说下单、支付回调、库存扣减这些真实闭环。我去年帮某高校实验室重构一个校园二手书流转系统时,就踩过这个坑——表面是「完整代码」,实际缺了 wx.login 与后端 session_key 解密的联调逻辑、商品列表分页的 cursor-based 实现、以及小程序端对 wx.requestPayment 的异常兜底处理。它真正该解决的,是「如何让一个小程序从扫码启动那一刻起,每一步网络请求、状态管理、页面跳转、错误提示都符合微信生态规范,并能直接部署上线跑通最小业务闭环」。适合两类人:一是刚学完基础 API 想做真实项目的前端新手,需要一份不跳步、不隐藏黑盒、每个 .js 文件都能打断点调试的参考;二是已有后台但缺小程序端经验的后端工程师,需要看清前端如何与云开发或自建服务安全对接。它不是教学 demo,而是一份带生产级配置、含真实接口契约、经真机扫码实测过的可交付工程骨架。


2. 用官方基础库 + 云开发快速搭出可运行骨架:初始化、登录态绑定与首页商品加载

2.1 创建项目并接入云开发环境:避免手动配 HTTPS 域名的玄学失败

微信开发者工具新建项目时,务必勾选「使用云开发」,并选择「一个云环境」。这一步省掉你后续所有域名白名单、TLS 证书、HTTPS 代理转发的折腾。云环境 ID 会自动注入 project.config.json,无需手改 appid 或 env:

{ "description": "项目配置文件", "packOptions": { "ignore": [] }, "setting": { "urlCheck": true, "es6": true, "enhance": true, "postcss": true, "minified": true, "newFeature": true, "coverView": true, "nodeModules": true, "autoAudits": false, "uploadWithSourceMap": true }, "compileType": "miniprogram", "libVersion": "2.30.2", "appid": "wx1234567890abcdef", "projectname": "mini-shop", "debugOptions": { "hidedInDevtools": [] }, "cloud": true, // ← 必须为 true "cloudfunctionRoot": "./cloudfunctions/", "cloudfunctionTemplateRoot": "./cloudfunctionTemplate/", "cloudfunctionFunctionRoot": "./cloudfunctions/", "cloudfunctionPackageRoot": "./cloudfunctions/", "cloudfunctionNodeModules": "./cloudfunctions/node_modules/", "condition": { "search": { "current": -1, "list": [] } } }

提示:"cloud": true是开关,漏写会导致wx.cloud.init()报错 “not initialized”。云环境 ID 在「云开发控制台」首页右上角复制,粘贴到app.js的wx.cloud.init()中,格式必须为'env-id-xxxxx'(不能带空格或引号外字符)。

2.2 登录态穿透:从 wx.login 到云函数获取 openid 的三步闭环

小程序端不能直接暴露app_secret,所以必须用云函数中转。常见错误是前端把code直接发给自建后端,导致签名验签失败或被劫持。正确路径是:前端调wx.login()→ 拿到code→ 传给云函数 → 云函数用cloud.callFunction调用微信服务端接口换取openid和unionid(需绑定公众号):

// pages/index/index.js Page({ data: { userInfo: null }, onLoad() { this.checkLogin(); }, async checkLogin() { try { const { code } = await wx.login(); // 步骤1:获取临时登录凭证 const res = await wx.cloud.callFunction({ name: 'login', // 云函数名,需提前部署 data: { code } }); const { openid, unionid } = res.result; this.setData({ userInfo: { openid, unionid } }); console.log('登录成功,openid:', openid); } catch (err) { console.error('登录失败', err); wx.showToast({ title: '登录异常,请重试', icon: 'none' }); } } });

云函数login/index.js内容如下(注意:APP_ID和APP_SECRET存于云开发环境变量,不在代码中硬编码):

// cloudfunctions/login/index.js const cloud = require('wx-server-sdk'); cloud.init({ env: cloud.DYNAMIC_CURRENT_ENV }); exports.main = async (event, context) => { const { code } = event; const APP_ID = process.env.APP_ID; // ← 从环境变量读取 const APP_SECRET = process.env.APP_SECRET; try { const result = await cloud.http.post({ url: `https://api.weixin.qq.com/sns/jscode2session?appid=${APP_ID}&secret=${APP_SECRET}&js_code=${code}&grant_type=authorization_code`, method: 'POST', dataType: 'json' }); const { openid, unionid, session_key } = result.data; if (!openid) throw new Error('获取 openid 失败:' + JSON.stringify(result.data)); // 将 openid 写入数据库,供后续订单关联 await cloud.database().collection('users').upsert({ data: { _id: openid, unionid, last_login: new Date() } }); return { openid, unionid }; } catch (err) { console.error('login 函数执行失败', err); throw err; } };

参数说明:cloud.http.post是云开发 2.0+ 新增能力,替代旧版wx-server-sdk的httpclient;process.env.APP_ID需在云开发控制台「环境变量」中手动添加,值为小程序后台「开发管理 > 开发者ID」里的 AppID;APP_SECRET同理,从「开发设置 > 开发者ID」下方「AppSecret」复制,切勿提交到 Git。

2.3 商品列表分页加载:用数据库 aggregate 实现真·无感滚动

很多所谓“完整代码”用skip/limit做分页,一到 1000 条数据就卡死。微信云开发推荐用aggregate+sort + limit+startAfter(游标分页),避免深度分页性能坍塌:

// cloudfunctions/getProducts/index.js exports.main = async (event, context) => { const { lastId, limit = 10 } = event; // lastId 是上一页最后一条的 _id 字符串 const db = cloud.database(); const $ = db.command.aggregate; try { let pipeline = [ { $match: { status: 'on_sale' } }, // 只查上架商品 { $sort: { _createTime: -1 } }, // 按创建时间倒序 { $limit: limit + 1 } // 多取 1 条,用于判断是否还有下一页 ]; if (lastId) { pipeline.unshift({ $match: { _id: db.command.gt(lastId) } }); } const res = await db.collection('products').aggregate() .addFields({ _idStr: $.toString('$_id') }) .pipeline(pipeline) .end(); const list = res.list || []; const hasNext = list.length > limit; const nextLastId = hasNext ? list[limit - 1]._id : null; return { list: list.slice(0, limit), hasNext, nextLastId }; } catch (err) { console.error('getProducts 执行失败', err); throw err; } };

前端调用时传入lastId(首次为空):

// pages/product/list.js data: { productList: [], hasNext: true, lastId: null, loading: false }, loadMore() { if (!this.data.hasNext || this.data.loading) return; this.setData({ loading: true }); wx.cloud.callFunction({ name: 'getProducts', data: { lastId: this.data.lastId, limit: 10 } }).then(res => { const { list, hasNext, nextLastId } = res.result; this.setData({ productList: this.data.productList.concat(list), hasNext, lastId: nextLastId, loading: false }); }).catch(err => { console.error('加载商品失败', err); this.setData({ loading: false }); }); },

关键细节:“_id: db.command.gt(lastId)” 是游标核心,要求_id是字符串类型且按时间有序(云开发默认 ObjectId 满足);addFields({ _idStr: $.toString('$_id') })是为兼容部分老版本 SDK 对_id类型的处理;limit + 1是为了准确判断是否到底,避免“刚好取满”导致误判。


3. 商品详情与购物车状态同步:本地缓存 + 云数据库双写一致性保障

3.1 商品详情页:用云数据库 watch 实现实时库存变化监听

用户在详情页停留时,若其他用户下单导致库存归零,页面应实时变灰“已售罄”。watch是云开发提供的 WebSocket 长连接能力,比轮询省资源、比手动 socket 更稳定:

// pages/product/detail.js Page({ data: { product: null, inStock: true, watcher: null }, onLoad(options) { const { id } = options; this.productId = id; this.loadProduct(id); this.startWatch(id); }, loadProduct(id) { wx.cloud.database().collection('products') .doc(id) .get() .then(res => { this.setData({ product: res.data, inStock: res.data.stock > 0 }); }); }, startWatch(id) { const db = wx.cloud.database(); this.setData({ watcher: db.collection('products').doc(id).watch({ onChange: (snapshot) => { if (snapshot.docChanges.length > 0) { const doc = snapshot.docChanges[0].doc; this.setData({ product: doc, inStock: doc.stock > 0 }); } }, onError: err => { console.error('监听失败', err); } }) }); }, onUnload() { if (this.data.watcher) { this.data.watcher.close(); // 必须手动关闭,否则内存泄漏 } } });

注意:watch仅支持collection().doc()单文档监听,不支持collection().where();首次get()是必须的,因为watch不返回初始数据;onUnload中关闭是硬性要求,否则切换页面后监听仍在后台运行,消耗云资源。

3.2 购物车本地缓存策略:wx.setStorageSync 的 10MB 边界与降级方案

微信限制wx.setStorageSync总容量为 10MB,但购物车数据结构复杂(含商品快照、规格选项、优惠券绑定),单次存 100 件商品就可能超限。我们采用「轻量 key + 云端 fallback」双写:

// utils/cart.js class CartManager { constructor() { this.key = 'cart_items'; } // 写入:只存必要字段,剔除图片、长描述等冗余 addItem(product) { const item = { id: product._id, title: product.title, price: product.price, stock: product.stock, count: 1, spec: product.spec || {}, _updateTime: Date.now() }; const cart = this.get(); const exist = cart.find(i => i.id === item.id && JSON.stringify(i.spec) === JSON.stringify(item.spec)); if (exist) { exist.count += 1; exist._updateTime = Date.now(); } else { cart.push(item); } try { wx.setStorageSync(this.key, cart); } catch (e) { // 超限时降级:清空本地,强制从云同步 console.warn('本地购物车写入失败,触发云端同步', e); this.syncToCloud(cart); } } get() { try { return wx.getStorageSync(this.key) || []; } catch (e) { console.warn('读取本地购物车失败,返回空数组'); return []; } } // 云端同步:当本地失效时,用云函数拉取最新 async syncToCloud(localCart) { try { const res = await wx.cloud.callFunction({ name: 'syncCart', data: { items: localCart } }); wx.setStorageSync(this.key, res.result.items); } catch (err) { console.error('云端同步失败', err); } } } module.exports = new CartManager();

血泪经验:wx.setStorageSync在 iOS 上对 JSON 深度嵌套敏感,超过 10 层易崩溃;所以spec字段我们只存{ color: 'red', size: 'M' }这类扁平结构,不存product.spec.options全量对象;_updateTime用于后续与云端时间戳比对冲突。

3.3 加入购物车原子操作:云函数内用 transaction 防超卖

前端加购不能只改本地缓存,必须调云函数扣减库存并写入购物车集合,且需事务保证“查库存 → 扣库存 → 写购物车”三步不中断:

// cloudfunctions/addToCart/index.js exports.main = async (event, context) => { const { productId, count = 1, spec = {} } = event; const db = cloud.database(); const transaction = db.transaction(); try { const productDoc = await transaction.collection('products').doc(productId).get(); const product = productDoc.data; if (!product || product.status !== 'on_sale') { throw new Error('商品不存在或已下架'); } if (product.stock < count) { throw new Error(`库存不足,当前剩余 ${product.stock}`); } // 扣减库存(原子操作) await transaction.collection('products').doc(productId).update({ data: { stock: db.command.inc(-count) } }); // 写入购物车(关联用户 openid) const openid = cloud.getWXContext().OPENID; await transaction.collection('cart_items').add({ data: { productId, openid, count, spec, createdAt: new Date(), updatedAt: new Date() } }); await transaction.commit(); // 提交事务 return { success: true, newStock: product.stock - count }; } catch (err) { await transaction.rollback(); // 回滚 console.error('addToCart 事务失败', err); throw err; } };

关键参数:db.command.inc(-count)是原子递减,比先get再update安全;cloud.getWXContext().OPENID是云函数内获取当前调用者 openid 的唯一可靠方式,前端传的不可信;transaction.rollback()必须显式调用,否则失败后库存已扣但购物车未写,造成数据不一致。


4. 支付与订单闭环:从 wx.requestPayment 到云函数生成预支付订单

4.1 生成预支付订单:云函数调统一下单接口并签名

微信支付要求所有参数由服务端生成并签名,前端只负责调起wx.requestPayment。云函数createOrder需完成:① 校验用户身份 ② 查询购物车 ③ 调unifiedorder接口 ④ 签名返回:

// cloudfunctions/createOrder/index.js const crypto = require('crypto'); exports.main = async (event, context) => { const { cartItemIds } = event; // 前端传入要结算的购物车项 ID 数组 const openid = cloud.getWXContext().OPENID; const db = cloud.database(); // 1. 校验购物车项归属 const cartItems = await db.collection('cart_items') .where({ _id: db.command.in(cartItemIds), openid }) .get(); if (cartItems.data.length !== cartItemIds.length) { throw new Error('购物车项不合法'); } // 2. 计算总金额(此处简化,实际需查商品最新价、优惠券) const totalFee = cartItems.data.reduce((sum, item) => sum + item.count * item.price, 0) * 100; // 单位:分 // 3. 调微信统一下单接口(需配置商户号、APIv3 密钥) const mch_id = process.env.MCH_ID; const api_v3_key = process.env.API_V3_KEY; const nonce_str = Math.random().toString(36).substr(2, 15); const out_trade_no = `ORD${Date.now()}${Math.floor(Math.random() * 1000)}`; const body = { appid: process.env.APP_ID, mch_id, nonce_str, out_trade_no, total_fee, spbill_create_ip: '127.0.0.1', // 云函数无真实 IP,填固定值 notify_url: `https://xxx.weixin.qq.com/api/pay/notify`, // 你的支付回调地址 trade_type: 'JSAPI', openid }; // 4. 生成签名(v3 签名规则) const signStr = Object.keys(body) .sort() .map(k => `${k}=${body[k]}`) .join('&') + `&key=${api_v3_key}`; const sign = crypto.createHash('md5').update(signStr, 'utf8').digest('hex').toUpperCase(); body.sign = sign; try { const res = await cloud.http.post({ url: 'https://api.mch.weixin.qq.com/pay/unifiedorder', method: 'POST', header: { 'Content-Type': 'application/xml' }, data: `<xml>${Object.keys(body).map(k => `<${k}>${body[k]}</${k}>`).join('')}</xml>` }); const xmlRes = await parseXML(res.data); // 需自行实现简单 XML 解析 if (xmlRes.return_code !== 'SUCCESS' || xmlRes.result_code !== 'SUCCESS') { throw new Error(`统一下单失败:${xmlRes.err_code_des}`); } // 5. 构造 JSAPI 参数(前端调 wx.requestPayment 用) const payParams = { appId: process.env.APP_ID, timeStamp: Math.floor(Date.now() / 1000).toString(), nonceStr: Math.random().toString(36).substr(2, 15), package: `prepay_id=${xmlRes.prepay_id}`, signType: 'RSA', paySign: '' // 此处需用 RSA 私钥签名,因篇幅略,见下节 }; return { ...payParams, prepay_id: xmlRes.prepay_id }; } catch (err) { console.error('createOrder 失败', err); throw err; } };

注意:api_v3_key是微信商户平台「API安全 > APIv3密钥」设置的32位字符串,绝不可泄露;notify_url必须是备案域名且支持 HTTPS;spbill_create_ip在云函数中无法获取真实客户端 IP,填127.0.0.1是微信接受的合法值。

4.2 前端调起支付:wx.requestPayment 的 5 个必填字段与超时兜底

wx.requestPayment参数必须与后端返回完全一致,且timeStamp必须是字符串(不是数字),否则 iOS 会静默失败:

// pages/order/confirm.js async handlePay() { try { const res = await wx.cloud.callFunction({ name: 'createOrder', data: { cartItemIds: this.data.selectedIds } }); // 构造支付参数(注意:timeStamp 必须是字符串!) const payment = { timeStamp: res.result.timeStamp, nonceStr: res.result.nonceStr, package: res.result.package, signType: res.result.signType, paySign: res.result.paySign }; await wx.requestPayment(payment); // 支付成功:跳转订单完成页 wx.navigateTo({ url: '/pages/order/success?outTradeNo=' + res.result.out_trade_no }); } catch (err) { if (err.errMsg?.includes('requestPayment:fail')) { wx.showToast({ title: '支付取消', icon: 'none' }); } else if (err.errMsg?.includes('requestPayment:fail cancel')) { wx.showToast({ title: '用户取消支付', icon: 'none' }); } else { wx.showToast({ title: '支付异常,请重试', icon: 'none' }); console.error('支付调起失败', err); } } }

玄学排查点:iOS 上timeStamp若为数字类型,wx.requestPayment会直接返回fail且无错误信息;必须toString();package字段值必须是prepay_id=xxx字符串,不能多空格或换行;signType必须大写RSA,小写rsa会失败。

4.3 支付结果异步通知:云函数接收并更新订单状态

微信服务器会 POST 到你配置的notify_url,云函数需验证签名、解密回调体、更新订单:

// cloudfunctions/payNotify/index.js const crypto = require('crypto'); const { createCipheriv, createDecipheriv } = require('crypto'); exports.main = async (event, context) => { const { headers, buffer } = event; // 云开发 v2.0+ 支持接收原始 body const signature = headers['wechatpay-signature']; const timestamp = headers['wechatpay-timestamp']; const nonce = headers['wechatpay-nonce']; // 1. 验证签名(略,需按微信文档拼接待签名串) const isValid = verifySignature(buffer, signature, timestamp, nonce, process.env.API_V3_KEY); if (!isValid) return { statusCode: 401, body: 'Invalid signature' }; // 2. 解密回调体(v3 接口返回 AES-GCM 加密) const associatedData = 'certificate'; const nonceBuf = Buffer.from(nonce, 'base64'); const cipherText = buffer.slice(0, -16); const authTag = buffer.slice(-16); const key = process.env.API_V3_KEY; // 32字节密钥 const decipher = createDecipheriv('aes-256-gcm', key, nonceBuf); decipher.setAuthTag(authTag); decipher.setAAD(Buffer.from(associatedData)); let decrypted; try { decrypted = Buffer.concat([decipher.update(cipherText), decipher.final()]); } catch (e) { console.error('解密失败', e); return { statusCode: 400, body: 'Decrypt failed' }; } const notifyData = JSON.parse(decrypted.toString()); // 3. 更新订单状态 if (notifyData.resource?.status === 'SUCCESS') { const outTradeNo = notifyData.out_trade_no; await cloud.database().collection('orders').doc(outTradeNo).update({ data: { status: 'paid', paidAt: new Date(), transactionId: notifyData.transaction_id } }); } return { statusCode: 200, body: '{"code":"SUCCESS","message":"OK"}' }; };

黑匣子提醒:微信支付回调体是 AES-GCM 加密,createDecipheriv的associatedData必须是字符串'certificate'(微信硬编码),不是证书内容;authTag是最后 16 字节,必须截取准确;解密失败时微信会重试,所以函数必须幂等(多次调用同一订单只更新一次)。


5. 避坑指南:6 个让小程序商店代码“看似能跑、实则上线即崩”的致命细节

5.1 现象:真机扫码白屏,开发者工具正常

原因:app.js中wx.cloud.init()调用位置错误。很多代码把init放在App()外部或onLaunch异步回调里,导致页面onLoad时云环境未就绪,wx.cloud.callFunction报 “not initialized”。
解决:必须在App()执行前同步调用,且确保env参数为字符串(不能是变量或undefined):

// ✅ 正确:app.js 顶部同步初始化 wx.cloud.init({ env: 'prod-xxxxx', // 字符串字面量 traceUser: true }); App({ onLaunch() { /* ... */ } });

5.2 现象:商品列表加载慢,滑动卡顿

原因:前端用wx:for渲染大量商品,且每个image组件未设宽高,导致微信渲染引擎反复重排;同时未开启lazy-load,首屏外图片仍抢占网络。
解决:所有image必须声明width和height(可用rpx),并加lazy-load属性;列表页page配置启用enablePullDownRefresh和onReachBottomDistance:

<!-- pages/product/list.wxml --> <view wx:for="{{productList}}" wx:key="_id" class="product-item"> <image src="{{item.cover}}" width="200rpx" height="200rpx" lazy-load mode="aspectFill"/> <text>{{item.title}}</text> </view>
// pages/product/list.json { "usingComponents": {}, "enablePullDownRefresh": true, "onReachBottomDistance": 50 }

5.3 现象:云函数部署后报 “Cannot find module ‘crypto’”

原因:云开发 Node.js 运行时默认为 10.x,而crypto模块在 10.x 中需require('crypto'),但部分代码用了require('node:crypto')(14+ 语法)。
解决:在云函数根目录package.json中明确指定"engines": { "node": "14.19.1" },并在云开发控制台「云函数」页点击「设置」→「运行环境」改为Node.js 14.19.1;同时检查所有require语句去掉node:前缀。

5.4 现象:用户登录后openid为空,或不同设备登录得到相同openid

原因:wx.login()未在用户授权后立即调用,而是放在wx.authorize成功回调里,但wx.authorize仅对scope.userInfo等用户信息授权有效,wx.login本身不需要授权,且code5 分钟过期,延迟调用极易失效。
解决:wx.login()必须独立调用,不依赖任何授权回调;登录态检查应封装为独立方法,在App.onLaunch和页面onLoad中主动触发,而非等待用户点击按钮:

// app.js App({ onLaunch() { this.checkLogin(); // 立即检查,不等用户操作 }, checkLogin() { wx.login({ // 独立调用,不包在 authorize 里 success: res => { // 传 code 给云函数 } }); } });

5.5 现象:支付成功后订单状态不更新,用户看到“待支付”

原因:notify_url配置的域名未在微信商户平台「产品中心 > 开发配置」中添加,或云函数payNotify未正确处理 POST 原始 body(云开发 v1.x 需用event.body并JSON.parse,v2.x 需用event.buffer)。
解决:确认商户平台「开发配置」中「支付授权目录」包含notify_url的完整路径(如https://xxx.weixin.qq.com/api/pay/notify);云函数中根据云开发版本选择解析方式——v2.x 用event.buffer,v1.x 用event.body并确保Content-Type: application/json。

5.6 现象:搜索商品时输入中文,数据库查不到结果

原因:云数据库where查询对中文支持弱,db.collection('products').where({ title: db.RegExp({ regexp: keyword, options: 'i' }) })在 keyword 含中文时可能匹配失败。
解决:改用aggregate的$regex操作符,并确保字段已建索引;更稳妥的是增加searchKeywords字段(拼音+分词),查询时匹配该字段:

// 云函数中预处理商品,存拼音关键词 const pinyin = require('pinyin'); const keywords = [...new Set([ ...pinyin(product.title, { style: pinyin.NORMAL }).flat(), ...product.title.split(/[\s,。!?;:""''()【】《》、]+/) ])].join(' '); await db.collection('products').doc(product._id).update({ data: { searchKeywords: keywords } }); // 查询时 db.collection('products').where({ searchKeywords: db.RegExp({ regexp: keyword, options: 'i' }) });

6. 真机调试与上线前 checklist:3 个我每次发布都手敲验证的命令

上线前最怕的不是功能缺失,而是那些“理论上应该没问题、但微信审核就是不通过”的隐性雷。我给自己定了铁律:每次提审前,必须在真机上跑通以下三步,并截图留档。这不是仪式感,是血泪换来的后悔药。

6.1 验证云函数全部可访问:用 curl 模拟微信服务端调用

微信审核会用服务端账号调你的云函数,如果函数权限设为「仅管理员可调用」,审核直接失败。必须用curl模拟真实调用链,确认返回 200:

# 替换 YOUR_ENV_ID 和 FUNCTION_NAME curl -X POST \ "https://YOUR_ENV_ID.tcb.qcloud.la/FUNCTION_NAME" \ -H "Content-Type: application/json" \ -d '{"code":"test_code"}' \ -w "\nHTTP Status: %{http_code}\n"

执行后必须看到HTTP Status: 200。若返回 401,说明云函数权限未设为「所有人可调用」;若返回 500,说明函数内有未捕获异常(比如process.env缺失);若超时,检查函数内存是否设为 256MB(最低要求)。

6.2 验证所有网络请求域名已备案并加入 request 合法域名

微信要求wx.request所有目标域名必须在「小程序管理后台 > 开发管理 > 服务器域名」中配置。但很多人只加了主域名,忘了加 CDN 或图片域名。我用这个脚本一键扫描项目里所有wx.request:

# 在项目根目录执行(需安装 ripgrep) rg -o "https?://[^\s)'\"]+" --no-filename | sort -u | while read url; do domain=$(echo $url | sed -E 's|https?://([^/]+).*|\1|'); echo "检查域名: $domain"; curl -I -s -o /dev/null -w "%{http_code}" https://$domain/ 2>/dev/null; done

输出中若有非200状态码,说明该域名未备案或 HTTPS 不可用,必须立刻处理。特别注意:https://api.weixin.qq.com是微信官方域名,无需添加;但https://xxx.qcloud.la(云开发域名)必须添加。

6.3 验证支付回调地址可被公网访问且响应正确

微信支付回调是 POST 请求,且要求响应体为{"code":"SUCCESS","message":"OK"},状态码 200。我用httpie发送模拟回调:

# 安装 httpie: pip install httpie http POST https://xxx.weixin.qq.com/api/pay/notify \ Content-Type:application/json \ '{"code":"SUCCESS","message":"OK"}'

必须看到HTTP/1.1 200 OK和响应体。若返回 404,说明路由未注册;若返回 500,检查云函数日志是否有ReferenceError: Buffer is not defined(Node.js 版本不匹配);若超时,确认云函数未设冷启动超时(应设为 60s)。

最后说一句:所谓“完整代码”,从来不是指文件数量多、UI 多炫酷,而是指从用户第一次扫码,到最后一笔订单支付成功,中间每一个环节——登录态、网络请求、状态同步、异常兜底、审核合规——都有对应代码覆盖,且每行都经真机验证。我至今保留着第一版商店代码的 commit message:“fix: login fail on iPhone XS due to timestamp type”,就为提醒自己:再小的细节,也是线上事故的入口。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询