网站开通微信支付完整配置教程
·
📋 目录
前言
本教程详细介绍如何在全栈应用中集成微信支付 Native 支付方式,实现会员充值、课程购买等付费功能。
技术栈
- 前端: React + TypeScript
- 后端: Edge Functions + Hono
- 支付: 微信支付 Native (扫码支付)
- 数据库: PostgreSQL
业务场景
- 会员充值(月度/季度/年度会员)
- 荣耀合伙人购买
- 其他增值服务
一、微信支付申请流程
1.1 注册微信支付商户号
- 访问 微信支付商户平台
- 点击"立即注册" → 选择"企业商户"
- 填写企业基本信息:
- 营业执照信息
- 法人身份证信息
- 对公账户信息
- 超级管理员信息
1.2 等待审核
- 审核周期:1-7 个工作日
- 审核通过后会收到开通通知
1.3 完成账户验证
- 微信会向对公账户打款(0.01-0.99元)
- 登录商户平台输入打款金额完成验证
1.4 申请产品功能
- 登录商户平台
- 进入"产品中心" → "我的产品"
- 申请开通 Native 支付(扫码支付)
二、获取必要的配置参数
2.1 AppID(应用 ID)
获取路径:微信公众平台 → 开发 → 基本配置
示例:wx1234567890abcdef
2.2 商户号(MCH_ID)
获取路径:微信支付商户平台 → 账户中心 → 商户信息
示例:1234567890
2.3 API 密钥(API Key)
设置路径:
- 微信支付商户平台 → 账户中心 → API安全
- 设置 APIv3 密钥(32位字符串)
- 重要:密钥设置后无法查看,请妥善保存
示例:abcdefghijklmnopqrstuvwxyz123456
2.4 API 证书
下载路径:微信支付商户平台 → 账户中心 → API安全 → 申请API证书
需要下载的文件:
apiclient_cert.pem- 证书文件apiclient_key.pem- 私钥文件apiclient_cert.p12- PKCS12 格式证书
证书序列号获取:
# 使用 OpenSSL 查看证书序列号
openssl x509 -in apiclient_cert.pem -noout -serial
三、环境变量配置
3.1 环境变量设置
在项目中设置以下环境变量:
| 变量名 | 说明 | 示例 |
|---|---|---|
WECHAT_APP_ID | 微信 AppID | wx1234567890abcdef |
WECHAT_MCH_ID | 微信商户号 | 1234567890 |
WECHAT_APP_SECRET | 微信 AppSecret | abc123def456... |
WECHAT_PRIVATE_KEY | API 私钥内容 | -----BEGIN PRIVATE KEY-----... |
WECHAT_CERT_SERIAL | API 证书序列号 | 5F3E... |
3.2 设置步骤
- 登录 Dashboard
- 选择您的项目
- 进入 Project Settings → Edge Functions → Secrets
- 点击 Add new secret,逐个添加上述环境变量
3.3 私钥格式注意事项
私钥需要保持完整的 PEM 格式,包括头尾标记:
-----BEGIN PRIVATE KEY-----
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC...
(省略中间内容)
...xyz
-----END PRIVATE KEY-----
四、后端服务器实现
4.1 项目结构
//functions/server/
├── index.tsx # 主服务器入口
├── wechat_pay.tsx # 微信支付核心逻辑
└── kv_store.tsx # 数据库操作工具
4.2 核心代码实现
4.2.1 微信支付工具类 (wechat_pay.tsx)
import * as crypto from "node:crypto";
export class WechatPay {
private appId: string;
private mchId: string;
private privateKey: crypto.KeyObject;
private certSerial: string;
constructor() {
this.appId = Deno.env.get("WECHAT_APP_ID")!;
this.mchId = Deno.env.get("WECHAT_MCH_ID")!;
this.certSerial = Deno.env.get("WECHAT_CERT_SERIAL")!;
const privateKeyPem = Deno.env.get("WECHAT_PRIVATE_KEY")!;
this.privateKey = crypto.createPrivateKey(privateKeyPem);
}
/**
* 生成签名
*/
private sign(message: string): string {
const sign = crypto.createSign("RSA-SHA256");
sign.update(message);
return sign.sign(this.privateKey, "base64");
}
/**
* 构建 Authorization 头
*/
private buildAuthorization(
method: string,
url: string,
body: string
): string {
const timestamp = Math.floor(Date.now() / 1000);
const nonce = crypto.randomBytes(16).toString("hex");
const signatureStr = [
method,
url,
timestamp,
nonce,
body,
].join("\n") + "\n";
const signature = this.sign(signatureStr);
return `WECHATPAY2-SHA256-RSA2048 mchid="${this.mchId}",nonce_str="${nonce}",signature="${signature}",timestamp="${timestamp}",serial_no="${this.certSerial}"`;
}
/**
* 创建 Native 支付订单
*/
async createNativeOrder(params: {
outTradeNo: string;
description: string;
amount: number;
notifyUrl: string;
}) {
const url = "/v3/pay/transactions/native";
const fullUrl = `https://api.mch.weixin.qq.com${url}`;
const body = {
appid: this.appId,
mchid: this.mchId,
description: params.description,
out_trade_no: params.outTradeNo,
notify_url: params.notifyUrl,
amount: {
total: params.amount,
currency: "CNY",
},
};
const bodyStr = JSON.stringify(body);
const authorization = this.buildAuthorization("POST", url, bodyStr);
const response = await fetch(fullUrl, {
method: "POST",
headers: {
"Content-Type": "application/json",
"Accept": "application/json",
"Authorization": authorization,
"User-Agent": "YourApp/1.0",
},
body: bodyStr,
});
if (!response.ok) {
const errorText = await response.text();
throw new Error(`微信支付API错误: ${response.status} ${errorText}`);
}
return await response.json();
}
/**
* 查询订单状态
*/
async queryOrder(outTradeNo: string) {
const url = `/v3/pay/transactions/out-trade-no/${outTradeNo}?mchid=${this.mchId}`;
const fullUrl = `https://api.mch.weixin.qq.com${url}`;
const authorization = this.buildAuthorization("GET", url, "");
const response = await fetch(fullUrl, {
method: "GET",
headers: {
"Accept": "application/json",
"Authorization": authorization,
"User-Agent": "YourApp/1.0",
},
});
if (!response.ok) {
const errorText = await response.text();
throw new Error(`查询订单失败: ${response.status} ${errorText}`);
}
return await response.json();
}
/**
* 验证回调签名
*/
verifyNotification(
timestamp: string,
nonce: string,
body: string,
signature: string,
serial: string
): boolean {
const message = `${timestamp}\n${nonce}\n${body}\n`;
// 这里简化处理,实际应该使用平台证书验证
// 完整实现需要下载并缓存微信支付平台证书
return true;
}
/**
* 解密回调数据
*/
decryptNotification(
ciphertext: string,
nonce: string,
associatedData: string
): string {
const apiKey = Deno.env.get("WECHAT_APP_SECRET")!;
const decipher = crypto.createDecipheriv(
"aes-256-gcm",
apiKey,
nonce
);
decipher.setAuthTag(Buffer.from(ciphertext.slice(-16), "base64"));
decipher.setAAD(Buffer.from(associatedData));
let decrypted = decipher.update(
ciphertext.slice(0, -16),
"base64",
"utf8"
);
decrypted += decipher.final("utf8");
return decrypted;
}
}
4.2.2 支付路由 (index.tsx)
import { Hono } from "npm:hono";
import { cors } from "npm:hono/cors";
import { logger } from "npm:hono/logger";
import { WechatPay } from "./wechat_pay.tsx";
import * as kv from "./kv_store.tsx";
const app = new Hono();
// 中间件
app.use("*", cors());
app.use("*", logger(console.log));
const wechatPay = new WechatPay();
/**
* 创建支付订单
*/
app.post("/make-server-3174ebb5/wechat/create-order", async (c) => {
try {
const { productType, amount, description, userId } = await c.req.json();
// 生成唯一订单号
const outTradeNo = `ORDER_${Date.now()}_${Math.random().toString(36).slice(2, 11)}`;
// 创建微信支付订单
const result = await wechatPay.createNativeOrder({
outTradeNo,
description,
amount: Math.floor(amount * 100), // 转为分
notifyUrl: `https://${Deno.env.get("_URL")}/functions/v1/make-server-3174ebb5/wechat/notify`,
});
// 保存订单信息到数据库
await kv.set(`wechat_order:${outTradeNo}`, {
outTradeNo,
productType,
amount,
description,
userId,
codeUrl: result.code_url,
status: "pending",
createdAt: new Date().toISOString(),
});
return c.json({
success: true,
outTradeNo,
codeUrl: result.code_url,
});
} catch (error: any) {
console.error("创建订单失败:", error);
return c.json({
success: false,
error: error.message,
}, 500);
}
});
/**
* 查询订单状态
*/
app.get("/make-server-3174ebb5/wechat/query-order/:outTradeNo", async (c) => {
try {
const { outTradeNo } = c.req.param();
// 先查本地数据库
const localOrder = await kv.get(`wechat_order:${outTradeNo}`);
if (localOrder?.status === "paid") {
return c.json({
success: true,
status: "paid",
order: localOrder,
});
}
// 查询微信支付
const result = await wechatPay.queryOrder(outTradeNo);
if (result.trade_state === "SUCCESS") {
// 更新本地订单状态
await kv.set(`wechat_order:${outTradeNo}`, {
...localOrder,
status: "paid",
paidAt: new Date().toISOString(),
transactionId: result.transaction_id,
});
// 处理业务逻辑(开通会员等)
await handleOrderSuccess(localOrder);
}
return c.json({
success: true,
status: result.trade_state === "SUCCESS" ? "paid" : "pending",
order: result,
});
} catch (error: any) {
console.error("查询订单失败:", error);
return c.json({
success: false,
error: error.message,
}, 500);
}
});
/**
* 微信支付回调
*/
app.post("/make-server-3174ebb5/wechat/notify", async (c) => {
try {
const body = await c.req.text();
const data = JSON.parse(body);
console.log("收到微信支付回调:", data);
// 验证签名
const { timestamp, nonce, signature, serial } = c.req.header();
const isValid = wechatPay.verifyNotification(
timestamp!,
nonce!,
body,
signature!,
serial!
);
if (!isValid) {
return c.json({ code: "FAIL", message: "签名验证失败" });
}
// 解密数据
const { resource } = data;
const decrypted = wechatPay.decryptNotification(
resource.ciphertext,
resource.nonce,
resource.associated_data
);
const orderData = JSON.parse(decrypted);
const outTradeNo = orderData.out_trade_no;
// 更新订单状态
const order = await kv.get(`wechat_order:${outTradeNo}`);
await kv.set(`wechat_order:${outTradeNo}`, {
...order,
status: "paid",
paidAt: new Date().toISOString(),
transactionId: orderData.transaction_id,
});
// 处理业务逻辑
await handleOrderSuccess(order);
return c.json({ code: "SUCCESS", message: "成功" });
} catch (error: any) {
console.error("处理回调失败:", error);
return c.json({ code: "FAIL", message: error.message });
}
});
/**
* 处理订单成功后的业务逻辑
*/
async function handleOrderSuccess(order: any) {
const { userId, productType } = order;
if (productType === "monthly_member") {
// 开通月度会员
const expiresAt = new Date();
expiresAt.setMonth(expiresAt.getMonth() + 1);
await kv.set(`user_member:${userId}`, {
type: "monthly",
startedAt: new Date().toISOString(),
expiresAt: expiresAt.toISOString(),
});
} else if (productType === "partner") {
// 开通荣耀合伙人
await kv.set(`user_partner:${userId}`, {
status: "active",
joinedAt: new Date().toISOString(),
});
}
console.log(`✅ 业务处理完成: ${productType} for user ${userId}`);
}
Deno.serve(app.fetch);
五、前端调用流程
5.1 创建支付组件
import { useState } from 'react';
import QRCode from 'qrcode';
export function PaymentModal({ productType, amount, description, userId, onClose }) {
const [qrCodeUrl, setQrCodeUrl] = useState('');
const [status, setStatus] = useState<'creating' | 'pending' | 'paid'>('creating');
const [outTradeNo, setOutTradeNo] = useState('');
// 创建订单
const createOrder = async () => {
try {
const response = await fetch(
`https://your-project.co/functions/v1/make-server-3174ebb5/wechat/create-order`,
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${publicAnonKey}`,
},
body: JSON.stringify({
productType,
amount,
description,
userId,
}),
}
);
const data = await response.json();
if (data.success) {
setOutTradeNo(data.outTradeNo);
// 生成二维码
const qr = await QRCode.toDataURL(data.codeUrl);
setQrCodeUrl(qr);
setStatus('pending');
// 开始轮询订单状态
startPolling(data.outTradeNo);
}
} catch (error) {
console.error('创建订单失败:', error);
}
};
// 轮询订单状态
const startPolling = (orderNo: string) => {
const interval = setInterval(async () => {
try {
const response = await fetch(
`https://your-project.co/functions/v1/make-server-3174ebb5/wechat/query-order/${orderNo}`,
{
headers: {
'Authorization': `Bearer ${publicAnonKey}`,
},
}
);
const data = await response.json();
if (data.status === 'paid') {
clearInterval(interval);
setStatus('paid');
// 3秒后关闭弹窗
setTimeout(() => {
onClose(true); // 支付成功
}, 3000);
}
} catch (error) {
console.error('查询订单失败:', error);
}
}, 2000); // 每2秒查询一次
// 5分钟后停止轮询
setTimeout(() => clearInterval(interval), 5 * 60 * 1000);
};
useEffect(() => {
createOrder();
}, []);
return (
{status === 'creating' &&
正在创建订单...
} {status === 'pending' && (
请使用微信扫码支付
金额: ¥{amount}
等待支付中...
)} {status === 'paid' && (
✅ 支付成功!
会员已开通
)}
); }
5.2 使用示例
import { PaymentModal } from './PaymentModal';
function MembershipPage() {
const [showPayment, setShowPayment] = useState(false);
const handleBuy = () => {
setShowPayment(true);
};
return (
购买月度会员 - ¥29.9 {showPayment && ( { setShowPayment(false); if (success) { // 刷新会员状态 } }} /> )}
); }
六、测试与验证
6.1 开发环境测试
- 配置沙箱环境
- 微信支付提供沙箱环境用于测试
- 访问:https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=23_1
- 使用测试账号
- 下载微信支付测试工具
- 使用测试商户号和密钥
6.2 测试流程
# 1. 启动本地服务
functions serve
# 2. 创建订单
curl -X POST http://localhost:54321/functions/v1/make-server-3174ebb5/wechat/create-order \
-H "Content-Type: application/json" \
-d '{
"productType": "monthly_member",
"amount": 29.9,
"description": "测试订单",
"userId": "test_user"
}'
# 3. 查询订单
curl http://localhost:54321/functions/v1/make-server-3174ebb5/wechat/query-order/ORDER_xxx
6.3 验证清单
- ☐ 订单创建成功,返回二维码
- ☐ 二维码可正常扫描
- ☐ 支付后订单状态更新
- ☐ 回调接口正常接收数据
- ☐ 会员状态正确开通
- ☐ 日志记录完整
更多推荐
所有评论(0)