微信小程序从0到1接入微信支付的完整攻略

本文面向有微信小程序开发经验的开发者,系统梳理微信支付从申请到上线的全流程,提供可直接使用的代码示例。

一、微信支付接入前置条件

1.1 资质要求

接入微信支付,你的小程序主体需要满足以下条件:

项目 要求
小程序主体 企业主体(个体工商户仅限部分类目)
小程序状态 已发布上线
营业执照 在有效期内
对公账户 用于结算(个体户可用法人对私账户)
法人身份证 正反面照片

个人开发者无法直接接入微信支付,但可以通过第三方服务商(如微信支付服务商平台)间接受理。

1.2 商户号申请流程

  1. 登录 微信支付商户平台,点击"成为商家"
  2. 填写营业执照信息、法人信息、结算账户
  3. 等待审核(通常1-3个工作日)
  4. 审核通过后,通过邮箱查收商户号(mch_id)和API密钥设置链接
  5. 登录商户平台,设置 APIv3 密钥,下载微信支付平台证书

关键配置项:

  • mch_id:商户号
  • appid:小程序的 AppID
  • APIv3 密钥:用于回调通知解密、敏感信息加解密
  • API 证书序列号:标识你的商户证书
  • 商户API私钥:签名使用,务必妥善保管

1.3 绑定小程序与商户号

在商户平台 → 产品中心 → AppID账号管理 中,关联你的小程序 AppID。这一步确保支付时能正确校验调用方身份。

二、完整接入流程

整体流程如下:

code复制

bash 复制代码
用户点击支付 → 小程序请求后端下单 → 后端调用统一下单API → 微信返回prepay_id
→ 后端签名返回支付参数 → 小程序调用wx.requestPayment → 用户完成支付
→ 微信异步回调后端 → 后端验签更新订单状态

2.1 配置支付域名

在商户平台 → 产品中心 → 开发配置 中,添加你的后端域名(仅 HTTPS)。同时在小程序管理后台 → 开发管理 → 开发设置中,配置 request合法域名 包含你的后端域名。

2.2 前端调起支付的参数

后端需要返回以下参数给前端:

javascript复制

bash 复制代码
{
  timeStamp: '1490840669',
  nonceStr: '5K8264ILTKCH16CQ2502SI8ZNMTM67VS',
  package: 'prepay_id=wx20170306425458994c08807f1909503344',
  signType: 'RSA',
  paySign: 'oR9d8PuhnIc+YZlz...'  // RSA签名
}

三、前端实现

3.1 完整的支付调用代码

javascript复制

bash 复制代码
// pages/order/confirm.js
const app = getApp()

Page({
  data: {
    orderInfo: null,
    paying: false
  },

  // 发起支付
  async handlePay() {
    if (this.data.paying) return
    this.setData({ paying: true })

    try {
      // 1. 先在本地创建订单(或后端创建)
      const orderId = await this.createOrder()

      // 2. 请求后端获取支付参数
      const payParams = await this.getPayParams(orderId)

      // 3. 调起微信支付
      await this.wxPay(payParams)

      // 4. 支付成功,跳转结果页
      wx.redirectTo({
        url: `/pages/pay/result?orderId=${orderId}&status=success`
      })
    } catch (err) {
      console.error('支付流程异常:', err)

      // 根据错误类型做不同处理
      if (err.errMsg && err.errMsg.includes('cancel')) {
        // 用户取消支付
        wx.redirectTo({
          url: `/pages/pay/result?status=cancel&orderId=${orderId}`
        })
      } else if (err.code === 'ORDER_EXPIRED') {
        wx.showModal({
          title: '订单已过期',
          content: '当前订单已超过支付时效,请重新下单',
          showCancel: false
        })
      } else {
        wx.showToast({
          title: '支付失败,请重试',
          icon: 'none'
        })
      }
    } finally {
      this.setData({ paying: false })
    }
  },

  // 调起wx.requestPayment
  wxPay(payParams) {
    return new Promise((resolve, reject) => {
      wx.requestPayment({
        timeStamp: payParams.timeStamp,
        nonceStr: payParams.nonceStr,
        package: payParams.package,
        signType: payParams.signType,
        paySign: payParams.paySign,

        success(res) {
          // 注意:success只代表用户完成了支付动作
          // 真正的支付结果以后端回调为准
          console.log('支付动作完成:', res)
          resolve(res)
        },

        fail(err) {
          console.error('支付失败:', err)
          reject(err)
        },

        // 支付状态查询(用户离开微信后的兜底)
        complete() {
          // 可在此处做轮询查单
        }
      })
    })
  },

  // 请求后端获取支付参数
  async getPayParams(orderId) {
    const res = await wx.request({
      url: `${app.globalData.apiBase}/pay/create`,
      method: 'POST',
      data: { orderId },
      header: {
        'Authorization': `Bearer ${wx.getStorageSync('token')}`
      }
    })

    if (res.statusCode !== 200 || !res.data.success) {
      throw new Error(res.data.message || '获取支付参数失败')
    }
    return res.data.data
  },

  // 创建订单
  async createOrder() {
    const res = await wx.request({
      url: `${app.globalData.apiBase}/order/create`,
      method: 'POST',
      data: {
        items: this.data.orderInfo.items,
        addressId: this.data.orderInfo.addressId,
        remark: this.data.remark
      }
    })
    return res.data.data.orderId
  }
})

3.2 支付结果页设计

javascript复制

bash 复制代码
// pages/pay/result.js
Page({
  onLoad(options) {
    const { status, orderId } = options
    this.setData({ status, orderId })

    // 如果是success状态,向后端确认真实支付结果
    if (status === 'success') {
      this.verifyPayResult(orderId)
    }
  },

  // 轮询确认支付结果(防止回调延迟)
  verifyPayResult(orderId) {
    let count = 0
    const maxRetry = 6
    const timer = setInterval(async () => {
      count++
      try {
        const res = await wx.request({
          url: `${getApp().globalData.apiBase}/order/${orderId}/pay-status`
        })
        if (res.data.data.paid) {
          clearInterval(timer)
          this.setData({ status: 'success', verified: true })
        } else if (count >= maxRetry) {
          clearInterval(timer)
          this.setData({ status: 'pending' })
        }
      } catch (e) {
        if (count >= maxRetry) clearInterval(timer)
      }
    }, 2000)
  },

  // 重新支付
  retryPay() {
    wx.redirectTo({
      url: `/pages/order/confirm?orderId=${this.data.orderId}`
    })
  },

  // 查看订单详情
  viewOrder() {
    wx.redirectTo({
      url: `/pages/order/detail?orderId=${this.data.orderId}`
    })
  },

  // 回到首页
  backHome() {
    wx.switchTab({ url: '/pages/index/index' })
  }
})

结果页 WXML:

xml复制

bash 复制代码
<!-- pages/pay/result.wxml -->
<view class="result-page">
  <!-- 成功 -->
  <block wx:if="{{status === 'success'}}">
    <view class="status-icon success-icon">✓</view>
    <view class="status-title">支付成功</view>
    <view class="status-desc" wx:if="{{verified}}">订单已确认,商家正在处理</view>
    <view class="status-desc" wx:else>支付结果确认中...</view>
    <view class="btn-group">
      <button class="btn-primary" bindtap="viewOrder">查看订单</button>
      <button class="btn-secondary" bindtap="backHome">回到首页</button>
    </view>
  </block>

  <!-- 取消 -->
  <block wx:elif="{{status === 'cancel'}}">
    <view class="status-icon cancel-icon">!</view>
    <view class="status-title">支付已取消</view>
    <view class="status-desc">订单尚未支付,可随时重新支付</view>
    <view class="btn-group">
      <button class="btn-primary" bindtap="retryPay">重新支付</button>
      <button class="btn-secondary" bindtap="viewOrder">查看订单</button>
    </view>
  </block>

  <!-- 失败 -->
  <block wx:elif="{{status === 'fail'}}">
    <view class="status-icon fail-icon">✕</view>
    <view class="status-title">支付失败</view>
    <view class="status-desc">{{errorMsg || '请检查网络后重试'}}</view>
    <view class="btn-group">
      <button class="btn-primary" bindtap="retryPay">重新支付</button>
      <button class="btn-secondary" bindtap="backHome">回到首页</button>
    </view>
  </block>

  <!-- 待确认 -->
  <block wx:elif="{{status === 'pending'}}">
    <view class="status-icon pending-icon">⏳</view>
    <view class="status-title">支付结果确认中</view>
    <view class="status-desc">支付结果正在确认,请稍后在订单详情页查看</view>
    <view class="btn-group">
      <button class="btn-primary" bindtap="viewOrder">查看订单</button>
      <button class="btn-secondary" bindtap="backHome">回到首页</button>
    </view>
  </block>
</view>

四、后端实现(Node.js)

4.1 项目依赖

bash复制

bash 复制代码
npm install @wechatpay/node-v3-utils axios crypto-js

4.2 支付配置模块

javascript复制

bash 复制代码
// config/pay.js
const fs = require('fs')
const path = require('path')

module.exports = {
  appId: process.env.WX_APPID,
  mchId: process.env.WX_MCH_ID,
  apiKey: process.env.WX_API_KEY,          // APIv2密钥(兼容用)
  apiV3Key: process.env.WX_API_V3_KEY,     // APIv3密钥
  serialNo: process.env.WX_SERIAL_NO,      // 商户证书序列号
  privateKey: fs.readFileSync(
    path.join(__dirname, '../certs/apiclient_key.pem'), 'utf8'
  ),
  notifyUrl: `${process.env.API_BASE}/pay/notify`,
  // 微信支付API基础URL
  baseUrl: 'https://api.mch.weixin.qq.com'
}

4.3 统一下单接口

javascript复制

bash 复制代码
// services/payService.js
const axios = require('axios')
const crypto = require('crypto')
const payConfig = require('../config/pay')
const { createSign, getAuthorization } = require('../utils/wxSign')

class PayService {
  /**
   * JSAPI下单(小程序支付)
   * @param {Object} params
   * @param {string} params.orderId      商户订单号
   * @param {number} params.amount      金额(元)
   * @param {string} params.description 商品描述
   * @param {string} params.openid       用户openid
   * @returns {Object} 支付参数(给前端调wx.requestPayment)
   */
  async createOrder({ orderId, amount, description, openid }) {
    // 1. 金额转分(微信支付单位为分)
    const total = Math.round(amount * 100)

    // 2. 生成商户订单号唯一标识
    const outTradeNo = `MINI_${orderId}_${Date.now()}`

    // 3. 构造请求体
    const body = {
      appid: payConfig.appId,
      mchid: payConfig.mchId,
      description: description.substring(0, 127),
      out_trade_no: outTradeNo,
      notify_url: payConfig.notifyUrl,
      amount: {
        total,
        currency: 'CNY'
      },
      payer: {
        openid
      }
    }

    // 4. 调用统一下单API
    const url = `${payConfig.baseUrl}/v3/pay/transactions/jsapi`
    const authorization = getAuthorization('POST', url, body)

    const res = await axios.post(url, body, {
      headers: {
        'Authorization': authorization,
        'Content-Type': 'application/json',
        'Accept': 'application/json'
      },
      timeout: 10000
    })

    const { prepay_id } = res.data

    // 5. 生成前端支付参数并签名
    const payParams = this.buildClientPayParams(prepay_id)

    // 6. 保存预支付记录
    await this.savePrepayRecord(outTradeNo, orderId, total, prepay_id)

    return {
      ...payParams,
      outTradeNo
    }
  }

  /**
   * 构造前端wx.requestPayment所需参数
   */
  buildClientPayParams(prepayId) {
    const timeStamp = Math.floor(Date.now() / 1000).toString()
    const nonceStr = crypto.randomBytes(16).toString('hex')

    // 构造签名串:appid\n时间戳\n随机串\nprepay_id\n
    const message = [
      payConfig.appId,
      timeStamp,
      nonceStr,
      prepayId
    ].join('\n')

    // RSA签名
    const sign = crypto.createSign('RSA-SHA256')
    sign.update(message)
    const paySign = sign.sign(payConfig.privateKey, 'base64')

    return {
      appId: payConfig.appId,
      timeStamp,
      nonceStr,
      package: `prepay_id=${prepayId}`,
      signType: 'RSA',
      paySign
    }
  }

  /**
   * 保存预支付记录(防重复下单)
   */
  async savePrepayRecord(outTradeNo, orderId, total, prepayId) {
    // 写入数据库或Redis
    // 防止同一订单重复下单
  }

  /**
   * 查询订单状态
   */
  async queryOrder(outTradeNo) {
    const url = `${payConfig.baseUrl}/v3/pay/transactions/out-trade-no/${outTradeNo}?mchid=${payConfig.mchId}`
    const authorization = getAuthorization('GET', url, '')

    const res = await axios.get(url, {
      headers: { 'Authorization': authorization }
    })

    return {
      tradeState: res.data.trade_state,
      tradeStateDesc: res.data.trade_state_desc,
      paid: res.data.trade_state === 'SUCCESS',
      transactionId: res.data.transaction_id,
      payerOpenid: res.data.payer?.openid,
      paidTime: res.data.success_time
    }
  }
}

module.exports = new PayService()

4.4 签名工具

javascript复制

bash 复制代码
// utils/wxSign.js
const crypto = require('crypto')
const payConfig = require('../config/pay')

/**
 * 生成请求签名头
 * HTTP方法\n请求URL\n请求体时间戳\n随机串\n
 */
function getAuthorization(method, url, body) {
  const timeStamp = Math.floor(Date.now() / 1000).toString()
  const nonceStr = crypto.randomBytes(16).toString('hex')

  const bodyStr = body ? JSON.stringify(body) : ''

  // 构造签名串
  const signMessage = [
    method,
    new URL(url).pathname,
    timeStamp,
    nonceStr,
    bodyStr
  ].join('\n')

  // RSA-SHA256签名
  const sign = crypto.createSign('RSA-SHA256')
  sign.update(signMessage)
  const signature = sign.sign(payConfig.privateKey, 'base64')

  // 构造Authorization头
  const authStr = [
    `WECHATPAY2-SHA256-RSA2048`,
    `mchid="${payConfig.mchId}"`,
    `nonce_str="${nonceStr}"`,
    `timestamp="${timeStamp}"`,
    `serial_no="${payConfig.serialNo}"`,
    `signature="${signature}"`
  ].join(', ')

  return authStr
}

/**
 * 验证回调通知签名
 */
function verifyNotifySign(headers, body, timestamp, nonce, signature) {
  const { 'wechatpay-timestamp': ts, 'wechatpay-nonce': n, 'wechatpay-serial': serial, 'wechatpay-signature': sig } = headers

  // 构造验签串
  const message = `${ts}\n${n}\n${body}\n`

  // 使用微信平台公钥验签(需提前下载)
  const verify = crypto.createVerify('RSA-SHA256')
  verify.update(message)
  return verify.verify(wechatPayPublicKey, sig, 'base64')
}

module.exports = { getAuthorization, verifyNotifySign }

4.5 回调通知处理

javascript复制

bash 复制代码
// controllers/payController.js
const express = require('express')
const crypto = require('crypto')
const payConfig = require('../config/pay')

const router = express.Router()

/**
 * 支付结果回调通知
 * 微信会以POST方式通知该接口,重复通知直到返回200
 */
router.post('/notify', async (req, res) => {
  try {
    // 1. 验证签名(防止伪造回调)
    const isValid = verifyNotifySign(req)
    if (!isValid) {
      console.error('回调签名验证失败')
      return res.status(401).json({ code: 'FAIL', message: '签名验证失败' })
    }

    // 2. 解密通知内容(AES-256-GCM)
    const { resource } = req.body
    const {
      ciphertext,
      associated_data,
      nonce
    } = resource

    const decrypted = decryptAES256GCM(
      ciphertext,
      associated_data,
      nonce,
      payConfig.apiV3Key
    )

    const orderData = JSON.parse(decrypted)

    // 3. 处理不同交易状态
    switch (orderData.trade_state) {
      case 'SUCCESS':
        await handlePaySuccess(orderData)
        break
      case 'NOTPAY':
        console.log('订单未支付:', orderData.out_trade_no)
        break
      case 'CLOSED':
        await handlePayClosed(orderData)
        break
      case 'REFUND':
        await handlePayRefund(orderData)
        break
      default:
        console.warn('未知交易状态:', orderData.trade_state)
    }

    // 4. 返回成功响应(重要!不返回200微信会重复通知)
    res.json({ code: 'SUCCESS', message: '成功' })

  } catch (err) {
    console.error('支付回调处理异常:', err)
    // 返回失败让微信重试
    res.status(500).json({ code: 'FAIL', message: err.message })
  }
})

/**
 * AES-256-GCM解密
 */
function decryptAES256GCM(ciphertext, associatedData, nonce, apiKey) {
  const decipher = crypto.createDecipheriv(
    'aes-256-gcm',
    Buffer.from(apiKey, 'utf8'),
    Buffer.from(nonce, 'utf8')
  )

  decipher.setAAD(Buffer.from(associatedData, 'utf8'))
  const ciphertextBuf = Buffer.from(ciphertext, 'base64')

  // GCM模式:密文末尾16字节为auth tag
  const authTag = ciphertextBuf.subarray(ciphertextBuf.length - 16)
  const encryptedData = ciphertextBuf.subarray(0, ciphertextBuf.length - 16)
  decipher.setAuthTag(authTag)

  const decrypted = Buffer.concat([
    decipher.update(encryptedData),
    decipher.final()
  ])

  return decrypted.toString('utf8')
}

/**
 * 处理支付成功逻辑
 */
async function handlePaySuccess(orderData) {
  const { out_trade_no, transaction_id, payer, success_time, amount } = orderData

  // 幂等性检查:防止重复处理同一笔回调
  const processed = await checkCallbackProcessed(transaction_id)
  if (processed) {
    console.log('回调已处理过:', transaction_id)
    return
  }

  // 更新订单状态
  await updateOrderStatus(out_trade_no, {
    paid: true,
    transactionId: transaction_id,
    payerOpenid: payer.openid,
    paidTime: success_time,
    paidAmount: amount.total
  })

  // 标记回调已处理
  await markCallbackProcessed(transaction_id)

  // 触发业务逻辑:发货通知、库存扣减等
  await triggerAfterPayHooks(out_trade_no)
}

module.exports = router

五、支付安全

5.1 防重复支付中间件

javascript复制

bash 复制代码
// middleware/payGuard.js

/**
 * 防重复支付中间件
 * 同一订单在短时间内只能发起一次支付
 */
function preventDuplicatePay(redisClient) {
  return async (req, res, next) => {
    const { orderId } = req.body
    const userId = req.user.id

    const lockKey = `pay_lock:${userId}:${orderId}`

    // 尝试获取分布式锁(5分钟过期)
    const acquired = await redisClient.set(lockKey, '1', {
      NX: true,
      EX: 300
    })

    if (!acquired) {
      return res.status(429).json({
        success: false,
        message: '支付请求处理中,请勿重复提交'
      })
    }

    res.locals.payLockKey = lockKey
    next()
  }
}

/**
 * 防金额篡改校验
 * 下单时记录金额,回调时比对
 */
async function verifyPayAmount(outTradeNo, actualTotal) {
  const order = await getOrder(outTradeNo)
  if (!order) throw new Error('订单不存在')

  // 订单已支付
  if (order.paid) throw new Error('订单已支付')

  // 金额比对
  const expectedTotal = Math.round(order.amount * 100)
  if (expectedTotal !== actualTotal) {
    console.error(`金额不一致: 预期${expectedTotal} 实际${actualTotal}`)
    throw new Error('支付金额与订单金额不一致')
  }
}

/**
 * 防回调伪造
 * 验证微信回调的签名 + IP白名单
 */
function validateCallbackSource(req) {
  // 1. 验证签名
  const signatureValid = verifyNotifySign(req)
  if (!signatureValid) return false

  // 2. IP白名单(微信回调IP段)
  const clientIp = req.ip
  const wechatPayIpRanges = [
    '101.226.', '101.227.', // 上海
    '180.163.', // 上海
    '140.207.', // 上海
    '203.205.', '203.205.147.' // 广州
  ]
  return wechatPayIpRanges.some(range => clientIp.startsWith(range))
}

module.exports = { preventDuplicatePay, verifyPayAmount, validateCallbackSource }

5.2 安全配置清单

javascript复制

bash 复制代码
// 安全检查清单
const SECURITY_CHECKLIST = {
  // 1. 密钥安全
  'API密钥不硬编码': '使用环境变量或密钥管理服务',
  '私钥文件权限': '仅应用服务可读 (chmod 400)',
  'APIv3密钥强度': '至少32位随机字符串',

  // 2. 签名验证
  '回调签名验证': '必须验签,不验签=裸奔',
  '请求签名': '所有API请求均需签名',
  '时间戳校验': '请求时间戳5分钟内有效',

  // 3. 业务安全
  '订单金额校验': '回调时比对原始订单金额',
  '幂等处理': '同一笔回调不重复处理',
  '分布式锁': '防止并发重复下单',

  // 4. 数据安全
  '敏感信息脱敏': '日志中不打印完整密钥/手机号',
  'HTTPS强制': '回调URL必须HTTPS',
  '数据库加密': '支付敏感字段加密存储'
}

六、常见问题排查

6.1 签名错误

症状return_code=FAIL, return_msg=签名错误

排查步骤

  1. 确认签名类型:V3接口使用 RSA-SHA256,不是 MD5
  2. 检查签名串格式:字段之间用\n分隔,不要有空格
  3. 确认商户证书序列号与密钥匹配
  4. 检查请求体是否与签名时使用的完全一致(JSON序列化顺序可能不同)

javascript复制

bash 复制代码
// 调试工具:打印签名串用于对比
function debugSign(method, url, body) {
  const bodyStr = body ? JSON.stringify(body) : ''
  console.log('签名原文:')
  console.log([method, new URL(url).pathname, '时间戳', '随机串', bodyStr].join('\n'))
}

6.2 订单过期

症状trade_state=NOTPAY 或前端报 ORDER_EXPIRED

解决方案

  • 微信预支付订单默认2小时过期,可通过 time_expire 参数自定义(最长24小时)
  • 建议在前端显示倒计时,过期前引导用户重新下单
  • 过期订单不需要手动关闭,微信会自动关闭

6.3 回调未收到

排查清单

code复制

bash 复制代码
□ 回调URL是否配置正确(商户平台→开发配置→回调地址)
□ 服务器是否能被公网访问
□ HTTPS证书是否有效
□ 是否正确返回了 { code: 'SUCCESS' } 响应体
□ 检查服务器防火墙和安全组规则
□ 查看微信商户平台→交易中心→订单查询中的通知状态

6.4 支付金额不一致

根本原因:前端传给后端的金额与实际支付金额存在精度问题。

javascript复制

bash 复制代码
// ✅ 正确:使用整数分计算
const totalFen = Math.round(parseFloat(amountYuan) * 100)

// ❌ 错误:浮点数直接计算
const totalFen = amountYuan * 100  // 0.1 * 100 = 10.000000000000002

强制规范:所有金额计算在服务端完成,前端仅传递数量和商品ID,后端查库计算金额。

七、微信支付新能力(2025+)

7.1 免密支付(委托代扣)

适用于周期性扣费场景(如会员续费、打车预扣费):

javascript复制

bash 复制代码
// 签约流程
// 1. 前端引导用户签署代扣协议
wx.navigateToMiniProgram({
  appId: 'wx1234567890abcdef', // 微信支付签约小程序
  path: 'pages/contract/index',
  extraData: {
    mch_id: '商户号',
    plan_id: '签约计划ID',
    contract_code: '合约号'
  }
})

// 2. 后端调用委托代扣API扣费
// POST /v3/pay/transactions/papay/apply

7.2 微信先享卡

一种"先享后付"的营销工具,用户承诺达标后享受优惠,未达标自动扣费:

code复制

bash 复制代码
接入流程:商户平台→产品中心→先享卡→申请→配置规则
用户路径:小程序内展示先享卡→用户领取→达标享优惠/未达标扣费

7.3 分账功能

适合平台型小程序(如外卖、分销):

javascript复制

bash 复制代码
// 统一下单时指定分账规则
const body = {
  // ...其他参数
  settle_info: {
    profit_sharing: true
  }
}

// 支付成功后调用分账API
// POST /v3/profitsharing/transactions

八、总结

微信支付接入的核心要点:

  1. 资质先行:企业主体、商户号、API密钥缺一不可
  2. 安全为本:签名验证、金额校验、幂等处理是底线
  3. 回调可靠:异步回调是确认支付的唯一标准,前端结果仅供参考
  4. 错误兜底:轮询查单作为回调的补充手段
  5. 日志完整:全链路记录请求/响应,问题排查时事半功倍

完整代码示例可在官方文档 微信支付开发者文档 中找到更多细节,建议与本文对照阅读。

相关推荐
spmcor2 天前
微信小程序 setStorageSync 踩坑实录:别让"顺手一存"变成"隐形炸弹"
微信小程序
用户4324281061142 天前
小程序埋点设计规范:如何设计可扩展的数据采集体系
微信小程序
玩烂小程序4 天前
微信小程序手串DIY功能开发实录:飞入动画 + 环形排布 + 拖拽换序 + 旋转查看 + 保存设计
微信小程序
何时梦醒4 天前
HTML5 Canvas 从入门到实战:手把手教你打造一款"打飞机"小游戏
微信小程序
master3364 天前
SSL 证书链问题导致微信小程序无法正常工作
网络协议·微信小程序·ssl
wuxia21185 天前
在5种环境中编写点击元素改变内容和颜色的JavaScript程序
javascript·微信小程序·vue·jquery·react
it-10245 天前
抖音快手短视频去水印微信小程序/一键去水印/小程序去水印接口代码
微信小程序·小程序·php
夏天测6 天前
微信小程序自动化漏洞挖掘流水线:从缓存提取到密钥验证全流程实战
python·网络安全·微信小程序·漏洞挖掘
it-10246 天前
微信小程序短视频去水印/抖音短视频去水印/免费去水印源码
微信小程序·小程序·视频去水印