微信相关能力

This commit is contained in:
2026-01-30 17:25:30 +08:00
parent 4856a88d41
commit bf591095a2
43 changed files with 4297 additions and 391 deletions

View File

@@ -33,6 +33,40 @@
|---------|------|------|
| `JUNHONG_JWT_SECRET_KEY` | JWT 签名密钥(生产环境必须修改) | `your-secret-key` |
### 微信配置
#### 微信公众号
| 环境变量 | 说明 | 示例 |
|---------|------|------|
| `JUNHONG_WECHAT_OFFICIAL_ACCOUNT_APP_ID` | 公众号 AppID必填 | `wxabcdef1234567890` |
| `JUNHONG_WECHAT_OFFICIAL_ACCOUNT_APP_SECRET` | 公众号 AppSecret必填 | `abcdef1234567890` |
| `JUNHONG_WECHAT_OFFICIAL_ACCOUNT_TOKEN` | 服务器配置Token可选 | `your_token` |
| `JUNHONG_WECHAT_OFFICIAL_ACCOUNT_AES_KEY` | 消息加解密Key可选 | `` |
| `JUNHONG_WECHAT_OFFICIAL_ACCOUNT_OAUTH_REDIRECT_URL` | OAuth回调URL可选 | `https://your-domain.com/callback` |
#### 微信支付
| 环境变量 | 说明 | 示例 |
|---------|------|------|
| `JUNHONG_WECHAT_PAYMENT_APP_ID` | 支付 AppID必填通常与公众号相同 | `wxabcdef1234567890` |
| `JUNHONG_WECHAT_PAYMENT_MCH_ID` | 商户号(必填) | `1234567890` |
| `JUNHONG_WECHAT_PAYMENT_API_V3_KEY` | APIv3 密钥必填32位字符串 | `your_apiv3_key_32_chars_here` |
| `JUNHONG_WECHAT_PAYMENT_API_V2_KEY` | APIv2 密钥(可选,部分接口需要) | `` |
| `JUNHONG_WECHAT_PAYMENT_CERT_PATH` | 商户证书路径(必填) | `/app/certs/apiclient_cert.pem` |
| `JUNHONG_WECHAT_PAYMENT_KEY_PATH` | 商户私钥路径(必填) | `/app/certs/apiclient_key.pem` |
| `JUNHONG_WECHAT_PAYMENT_SERIAL_NO` | 证书序列号(必填) | `1234567890ABCDEF` |
| `JUNHONG_WECHAT_PAYMENT_NOTIFY_URL` | 支付回调URL必填 | `https://api.your-domain.com/api/callback/wechat-pay` |
| `JUNHONG_WECHAT_PAYMENT_HTTP_DEBUG` | HTTP调试日志可选 | `false` |
| `JUNHONG_WECHAT_PAYMENT_TIMEOUT` | HTTP请求超时可选 | `30s` |
**配置说明**
- 微信公众号和支付配置缺失时服务启动会失败FATAL 错误)
- 证书文件必须可读(权限 600 或 644
- APIv3 密钥必须是 32 位字符串
- 证书序列号可通过 `openssl x509 -in apiclient_cert.pem -noout -serial` 获取
- 详细配置指南参见 [微信集成使用指南](wechat-integration/使用指南.md)
## 可选配置
以下配置有合理的默认值,可按需覆盖:

View File

@@ -0,0 +1,564 @@
# 微信集成 API 文档
本文档详细说明微信 OAuth 登录和微信支付相关的 API 接口。
## 目录
- [认证说明](#认证说明)
- [错误码](#错误码)
- [API 接口](#api-接口)
- [1. 微信 OAuth 登录](#1-微信-oauth-登录)
- [2. 绑定微信账号](#2-绑定微信账号)
- [3. 微信 JSAPI 支付](#3-微信-jsapi-支付)
- [4. 微信 H5 支付](#4-微信-h5-支付)
- [5. 微信支付回调](#5-微信支付回调)
---
## 认证说明
### 公开接口
以下接口无需认证,可直接调用:
- `POST /api/c/v1/wechat/auth` - 微信 OAuth 登录
- `POST /api/callback/wechat-pay` - 微信支付回调
### 需要认证的接口
以下接口需要在请求头中携带 JWT Token
```
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
```
- `POST /api/c/v1/bind-wechat` - 绑定微信账号(个人客户)
- `POST /api/h5/orders/:id/wechat-pay/jsapi` - 微信 JSAPI 支付H5认证
- `POST /api/h5/orders/:id/wechat-pay/h5` - 微信 H5 支付H5认证
---
## 错误码
微信集成相关的错误码:
| 错误码 | 说明 | HTTP 状态码 |
|--------|------|-------------|
| 1044 | 微信 OAuth 授权失败 | 400 |
| 1045 | 获取微信用户信息失败 | 400 |
| 1046 | 微信支付失败 | 400 |
| 1047 | 微信支付回调数据无效 | 400 |
| 1003 | 参数无效 | 400 |
| 1020 | 手机号已被使用 | 400 |
| 1021 | 个人客户不存在 | 404 |
| 1035 | 订单不存在 | 404 |
---
## API 接口
### 1. 微信 OAuth 登录
通过微信授权码登录或创建账号。如果用户首次登录,系统会自动创建账号。
**接口地址**
```
POST /api/c/v1/wechat/auth
```
**请求参数**
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| code | string | ✅ | 微信授权码5分钟有效期一次性使用 |
**请求示例**
```json
{
"code": "071abc123456789def"
}
```
**响应参数**
| 参数 | 类型 | 说明 |
|------|------|------|
| code | integer | 响应码0表示成功 |
| msg | string | 响应消息 |
| data | object | 响应数据 |
| data.token | string | JWT Token用于后续请求认证 |
| data.customer_id | integer | 个人客户ID |
| data.phone | string | 手机号(未绑定时为空) |
| data.nickname | string | 昵称(微信昵称) |
| data.is_new_user | boolean | 是否新用户 |
| timestamp | string | 响应时间戳RFC3339格式 |
**响应示例**
```json
{
"code": 0,
"msg": "登录成功",
"data": {
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJjdXN0b21lcl9pZCI6MTIzLCJleHAiOjE3MDY2OTI4MDB9.abc123def456",
"customer_id": 123,
"phone": "138****8888",
"nickname": "微信用户",
"is_new_user": false
},
"timestamp": "2025-01-30T12:00:00+08:00"
}
```
**错误响应**
```json
{
"code": 1044,
"msg": "微信 OAuth 授权失败",
"timestamp": "2025-01-30T12:00:00+08:00"
}
```
**业务逻辑**
1. 验证授权码是否有效
2. 调用微信API获取用户 OpenID 和 UnionID
3. 查找数据库是否存在该微信用户:
- **存在**:返回已有账号的 Token
- **不存在**:创建新账号,返回新账号的 Token
4. 新用户状态为"未绑定手机号",后续需要绑定手机号才能使用完整功能
**注意事项**
- 授权码code只能使用一次重复使用会失败
- 授权码有效期为5分钟
- Token 有效期为7天
- 新用户首次登录时 `phone` 字段为空,需要引导绑定手机号
---
### 2. 绑定微信账号
将当前登录的个人客户账号绑定到微信。
**接口地址**
```
POST /api/c/v1/bind-wechat
```
**认证方式**
需要携带 JWT Token个人客户
**请求参数**
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| code | string | ✅ | 微信授权码 |
**请求示例**
```json
{
"code": "071abc123456789def"
}
```
**响应参数**
| 参数 | 类型 | 说明 |
|------|------|------|
| code | integer | 响应码0表示成功 |
| msg | string | 响应消息 |
| timestamp | string | 响应时间戳 |
**响应示例**
```json
{
"code": 0,
"msg": "绑定成功",
"timestamp": "2025-01-30T12:00:00+08:00"
}
```
**错误响应**
```json
{
"code": 1020,
"msg": "该微信号已被其他账号绑定",
"timestamp": "2025-01-30T12:00:00+08:00"
}
```
**业务逻辑**
1. 验证授权码是否有效
2. 获取微信 OpenID 和 UnionID
3. 检查该微信号是否已被其他账号绑定
4. 更新当前账号的微信绑定信息
**注意事项**
- 一个微信号只能绑定一个账号
- 绑定后无法解绑(需联系管理员)
- 绑定成功后,可以使用微信登录
---
### 3. 微信 JSAPI 支付
创建微信 JSAPI 支付订单(微信内网页支付)。
**接口地址**
```
POST /api/h5/orders/:id/wechat-pay/jsapi
```
**认证方式**
需要携带 H5 Token。
**路径参数**
| 参数 | 类型 | 说明 |
|------|------|------|
| id | integer | 订单ID |
**请求参数**
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| open_id | string | ✅ | 用户的微信 OpenID在公众号内获取 |
**请求示例**
```json
{
"open_id": "o6_bmjrPTlm6_2sgVt7hMZOPfL2M"
}
```
**响应参数**
| 参数 | 类型 | 说明 |
|------|------|------|
| code | integer | 响应码0表示成功 |
| msg | string | 响应消息 |
| data | object | 响应数据 |
| data.prepay_id | string | 预支付交易会话标识 |
| data.pay_config | object | 支付配置直接传给微信JSAPI |
| data.pay_config.appId | string | 公众号AppID |
| data.pay_config.timeStamp | string | 时间戳 |
| data.pay_config.nonceStr | string | 随机字符串 |
| data.pay_config.package | string | 订单详情扩展字符串 |
| data.pay_config.signType | string | 签名方式RSA |
| data.pay_config.paySign | string | 签名 |
| timestamp | string | 响应时间戳 |
**响应示例**
```json
{
"code": 0,
"msg": "支付订单创建成功",
"data": {
"prepay_id": "wx30123456789012345678901234567890",
"pay_config": {
"appId": "wxabcdef1234567890",
"timeStamp": "1706606400",
"nonceStr": "5K8264ILTKCH16CQ2502SI8ZNMTM67VS",
"package": "prepay_id=wx30123456789012345678901234567890",
"signType": "RSA",
"paySign": "oR9d8PuhnIc+YZ8cBHFCwfgpaK9gd7JS..."
}
},
"timestamp": "2025-01-30T12:00:00+08:00"
}
```
**错误响应**
```json
{
"code": 1035,
"msg": "订单不存在",
"timestamp": "2025-01-30T12:00:00+08:00"
}
```
**业务逻辑**
1. 验证订单是否存在且状态为"待支付"
2. 验证订单归属(只能支付自己的订单)
3. 调用微信支付API创建预支付订单
4. 生成支付配置(包含签名)
5. 返回支付配置给前端
**前端调用示例**
```javascript
// 获取支付配置
const res = await fetch(`/api/h5/orders/${orderId}/wechat-pay/jsapi`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${h5Token}`
},
body: JSON.stringify({ open_id: openId })
});
const result = await res.json();
// 调用微信JSAPI支付
wx.chooseWXPay({
...result.data.pay_config,
success: function(res) {
console.log('支付成功', res);
},
fail: function(res) {
console.log('支付失败', res);
}
});
```
**注意事项**
- 只能在微信内网页中使用
- OpenID 需要通过公众号 OAuth 获取
- 支付有效期为2小时
- 订单只能支付一次
---
### 4. 微信 H5 支付
创建微信 H5 支付订单(微信外浏览器支付)。
**接口地址**
```
POST /api/h5/orders/:id/wechat-pay/h5
```
**认证方式**
需要携带 H5 Token。
**路径参数**
| 参数 | 类型 | 说明 |
|------|------|------|
| id | integer | 订单ID |
**请求参数**
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| scene_info | object | ❌ | 场景信息 |
| scene_info.payer_client_ip | string | ❌ | 用户客户端IP |
| scene_info.h5_type | string | ❌ | H5类型Wap/IOS/Android |
**请求示例**
```json
{
"scene_info": {
"payer_client_ip": "123.12.12.123",
"h5_type": "Wap"
}
}
```
**响应参数**
| 参数 | 类型 | 说明 |
|------|------|------|
| code | integer | 响应码0表示成功 |
| msg | string | 响应消息 |
| data | object | 响应数据 |
| data.h5_url | string | H5 支付跳转链接 |
| timestamp | string | 响应时间戳 |
**响应示例**
```json
{
"code": 0,
"msg": "H5 支付订单创建成功",
"data": {
"h5_url": "https://wx.tenpay.com/cgi-bin/mmpayweb-bin/checkmweb?prepay_id=wx30123456789012345678901234567890&package=3583359058"
},
"timestamp": "2025-01-30T12:00:00+08:00"
}
```
**前端调用示例**
```javascript
// 创建 H5 支付订单
const res = await fetch(`/api/h5/orders/${orderId}/wechat-pay/h5`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${h5Token}`
},
body: JSON.stringify({
scene_info: {
payer_client_ip: clientIp,
h5_type: 'Wap'
}
})
});
const result = await res.json();
// 跳转到微信 H5 支付页面
if (result.code === 0) {
const returnUrl = encodeURIComponent(`https://your-domain.com/orders/${orderId}`);
window.location.href = `${result.data.h5_url}&redirect_url=${returnUrl}`;
}
```
**注意事项**
- 适用于微信外浏览器
- 支付完成后会跳转到 `redirect_url`需URL编码
- 支付有效期为5分钟
- 需要在微信商户平台配置 H5 支付域名
---
### 5. 微信支付回调
接收微信支付的异步通知。
**接口地址**
```
POST /api/callback/wechat-pay
```
**认证方式**
无需认证(由微信签名验证)。
**请求说明**
该接口由微信支付系统调用,开发者无需主动调用。
**请求头**
| 参数 | 说明 |
|------|------|
| Wechatpay-Serial | 微信支付平台证书序列号 |
| Wechatpay-Signature | 微信签名 |
| Wechatpay-Timestamp | 微信时间戳 |
| Wechatpay-Nonce | 微信随机串 |
**请求体**
微信发送的加密数据JSON格式
**响应**
成功处理返回 HTTP 200
```json
{
"code": "SUCCESS",
"message": "成功"
}
```
失败返回 HTTP 500
```json
{
"code": "FAIL",
"message": "失败原因"
}
```
**处理流程**
1. 验证微信签名PowerWeChat 自动处理)
2. 解密通知数据
3. 提取支付结果(交易状态、金额、订单号等)
4. 更新订单状态为"已支付"
5. 触发异步任务:
- 分佣计算
- 套餐分配
- 钱包充值
6. 返回成功响应给微信
**幂等性保证**
系统会检查订单状态,避免重复处理:
- 如果订单已支付,直接返回成功
- 如果订单不存在,返回失败
- 使用数据库事务确保原子性
**重试机制**
微信会在以下情况重试:
- 商户系统未返回响应
- 返回 HTTP 状态码不是 200
- 返回结果为 FAIL
重试规则:
- 15秒后第1次重试
- 30秒后第2次重试
- 3分钟后第3次重试
- 最多重试3次
**注意事项**
- 接口必须在 **10秒内** 返回响应
- 必须返回正确的 JSON 格式
- 签名验证失败会记录日志但不影响服务
- 处理失败会自动重试,无需手动干预
---
## 测试建议
### 开发环境测试
1. **OAuth 登录测试**
- 使用微信测试号(公众号测试账号)
- 在本地配置内网穿透ngrok、frp等
- 测试授权流程和账号创建
2. **支付功能测试**
- 使用 0.01 元小额订单测试
- 验证支付流程和回调处理
- 测试完成后可通过退款功能退回
3. **回调测试**
- 使用微信支付沙箱环境(需申请)
- 或者使用 Postman 模拟回调请求
- 验证幂等性和重试机制
### 生产环境测试
1. 使用真实商户号和公众号
2. 配置正确的 HTTPS 域名
3. 小额订单测试(建议 0.01 元)
4. 监控日志确认回调正常
---
## 相关文档
- [使用指南](./使用指南.md) - 详细的配置和部署说明
- [环境变量配置](../environment-variables.md) - 所有环境变量说明
- [README](../../README.md) - 项目整体说明

View File

@@ -0,0 +1,562 @@
# 微信公众号与微信支付集成使用指南
本文档说明如何配置和使用系统的微信公众号 OAuth 认证和微信支付功能。
## 目录
- [概述](#概述)
- [前置条件](#前置条件)
- [配置步骤](#配置步骤)
- [1. 微信公众号配置](#1-微信公众号配置)
- [2. 微信支付配置](#2-微信支付配置)
- [3. 证书文件配置](#3-证书文件配置)
- [环境变量配置](#环境变量配置)
- [功能说明](#功能说明)
- [微信 OAuth 登录](#微信-oauth-登录)
- [微信 JSAPI 支付](#微信-jsapi-支付)
- [微信 H5 支付](#微信-h5-支付)
- [支付回调处理](#支付回调处理)
- [常见问题](#常见问题)
---
## 概述
系统集成了以下微信功能:
1. **微信公众号 OAuth 认证**:个人客户可以通过微信授权码登录/绑定账号
2. **微信 JSAPI 支付**:支持微信内网页支付
3. **微信 H5 支付**:支持微信外浏览器 H5 支付
4. **支付回调处理**:自动验证微信支付签名并处理回调
技术实现使用 [PowerWeChat v3 SDK](https://github.com/ArtisanCloud/PowerWeChat)。
---
## 前置条件
在开始配置之前,您需要:
1. **微信公众号**(已认证)
- 公众号 AppID
- 公众号 AppSecret
- OAuth 回调域名(需在公众号后台配置)
2. **微信商户号**(已开通)
- 商户号 MchID
- APIv3 密钥32位字符串
- APIv2 密钥(可选,部分接口需要)
- 商户证书apiclient_cert.pem
- 商户私钥apiclient_key.pem
- 证书序列号
3. **服务器环境**
- 可访问的 HTTPS 域名(用于接收微信回调)
- Redis用于缓存 AccessToken
---
## 配置步骤
### 1. 微信公众号配置
#### 1.1 获取 AppID 和 AppSecret
登录 [微信公众平台](https://mp.weixin.qq.com/),在"开发" → "基本配置"中获取:
- AppID应用ID
- AppSecret应用密钥
#### 1.2 配置 OAuth 回调域名
在"设置与开发" → "公众号设置" → "功能设置" → "网页授权域名"中配置:
```
your-domain.com
```
**注意**
- 不要带 `http://``https://`
- 不要带端口号
- 需要验证域名所有权(下载验证文件到网站根目录)
### 2. 微信支付配置
#### 2.1 获取商户信息
登录 [微信支付商户平台](https://pay.weixin.qq.com/)
1. **商户号MchID**:在"账户中心" → "商户信息"中查看
2. **APIv3 密钥**:在"账户中心" → "API安全" → "设置APIv3密钥"中设置32位字符串
3. **APIv2 密钥**可选同上设置API密钥32位字符串
#### 2.2 下载商户证书
在"账户中心" → "API安全" → "申请API证书"
1. 下载证书工具
2. 生成证书请求文件
3. 上传请求文件
4. 下载证书文件:
- `apiclient_cert.pem`(商户证书)
- `apiclient_key.pem`(商户私钥)
#### 2.3 获取证书序列号
**方法1使用 OpenSSL**
```bash
openssl x509 -in apiclient_cert.pem -noout -serial | cut -d= -f2
```
**方法2从商户平台查看**
在"账户中心" → "API安全" → "API证书"中查看证书序列号。
#### 2.4 配置支付回调 URL
在"产品中心" → "开发配置" → "支付配置"中设置:
```
https://your-domain.com/api/callback/wechat-pay
```
**注意**
- 必须使用 HTTPS
- 确保服务器可以接收微信的 POST 请求
### 3. 证书文件配置
将下载的证书文件放置到服务器:
```bash
# 创建证书目录
mkdir -p /app/certs
# 复制证书文件
cp apiclient_cert.pem /app/certs/
cp apiclient_key.pem /app/certs/
# 设置文件权限(仅所有者可读写)
chmod 600 /app/certs/*
```
**Docker 部署**:在 `docker-compose.yml` 中挂载证书目录:
```yaml
services:
api:
volumes:
- ./certs:/app/certs:ro # 只读挂载
```
---
## 环境变量配置
`.env.local` 或生产环境中设置以下环境变量:
```bash
# ===== 微信公众号配置 =====
export JUNHONG_WECHAT_OFFICIAL_ACCOUNT_APP_ID="wxabcdef1234567890"
export JUNHONG_WECHAT_OFFICIAL_ACCOUNT_APP_SECRET="your_app_secret_here"
export JUNHONG_WECHAT_OFFICIAL_ACCOUNT_TOKEN="" # 可选,服务器配置用
export JUNHONG_WECHAT_OFFICIAL_ACCOUNT_AES_KEY="" # 可选,消息加解密用
export JUNHONG_WECHAT_OFFICIAL_ACCOUNT_OAUTH_REDIRECT_URL="" # 可选自定义回调URL
# ===== 微信支付配置 =====
export JUNHONG_WECHAT_PAYMENT_APP_ID="wxabcdef1234567890" # 与公众号 AppID 相同
export JUNHONG_WECHAT_PAYMENT_MCH_ID="1234567890"
export JUNHONG_WECHAT_PAYMENT_API_V3_KEY="your_apiv3_key_32_chars_here"
export JUNHONG_WECHAT_PAYMENT_API_V2_KEY="" # 可选,部分接口需要
export JUNHONG_WECHAT_PAYMENT_CERT_PATH="/app/certs/apiclient_cert.pem"
export JUNHONG_WECHAT_PAYMENT_KEY_PATH="/app/certs/apiclient_key.pem"
export JUNHONG_WECHAT_PAYMENT_SERIAL_NO="1234567890ABCDEF"
export JUNHONG_WECHAT_PAYMENT_NOTIFY_URL="https://your-domain.com/api/callback/wechat-pay"
export JUNHONG_WECHAT_PAYMENT_HTTP_DEBUG=false
export JUNHONG_WECHAT_PAYMENT_TIMEOUT="30s"
```
**配置说明**
| 配置项 | 必填 | 说明 |
|--------|------|------|
| `OFFICIAL_ACCOUNT_APP_ID` | ✅ | 公众号 AppID |
| `OFFICIAL_ACCOUNT_APP_SECRET` | ✅ | 公众号 AppSecret |
| `PAYMENT_APP_ID` | ✅ | 支付 AppID通常与公众号相同 |
| `PAYMENT_MCH_ID` | ✅ | 商户号 |
| `PAYMENT_API_V3_KEY` | ✅ | APIv3 密钥32位 |
| `PAYMENT_CERT_PATH` | ✅ | 商户证书路径 |
| `PAYMENT_KEY_PATH` | ✅ | 商户私钥路径 |
| `PAYMENT_SERIAL_NO` | ✅ | 证书序列号 |
| `PAYMENT_NOTIFY_URL` | ✅ | 支付回调 URL |
| `PAYMENT_TIMEOUT` | ❌ | HTTP 请求超时默认30s |
| `PAYMENT_HTTP_DEBUG` | ❌ | 开启 HTTP 调试日志 |
---
## 功能说明
### 微信 OAuth 登录
#### 业务流程
```
1. 前端引导用户点击"微信登录"
2. 跳转到微信授权页面微信SDK处理
3. 用户同意授权后,微信回调到前端
4. 前端获取授权码code调用后端登录接口
5. 后端通过 code 获取用户 OpenID/UnionID
6. 后端创建/查找用户,返回 JWT Token
```
#### API 端点
**POST `/api/c/v1/wechat/auth`**
请求体:
```json
{
"code": "071abc123456789def"
}
```
响应:
```json
{
"code": 0,
"msg": "登录成功",
"data": {
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"customer_id": 123,
"phone": "138****8888",
"nickname": "微信用户",
"is_new_user": false
},
"timestamp": "2025-01-30T12:00:00Z"
}
```
#### 前端集成示例
```javascript
// 1. 构造微信授权 URL前端处理
const redirectUri = encodeURIComponent('https://your-domain.com/wechat-callback');
const appId = 'wxabcdef1234567890';
const scope = 'snsapi_userinfo'; // 或 snsapi_base静默授权
const state = 'STATE'; // 自定义参数
const authUrl = `https://open.weixin.qq.com/connect/oauth2/authorize?appid=${appId}&redirect_uri=${redirectUri}&response_type=code&scope=${scope}&state=${state}#wechat_redirect`;
// 跳转到微信授权页面
window.location.href = authUrl;
// 2. 在回调页面获取 code 并调用后端
const urlParams = new URLSearchParams(window.location.search);
const code = urlParams.get('code');
fetch('https://api.your-domain.com/api/c/v1/wechat/auth', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ code })
})
.then(res => res.json())
.then(data => {
if (data.code === 0) {
localStorage.setItem('token', data.data.token);
// 跳转到主页
}
});
```
### 微信 JSAPI 支付
#### 业务流程
```
1. 前端调用后端创建支付订单
2. 后端调用微信支付接口,获取 prepay_id 和支付配置
3. 前端调用微信 JSAPI 唤起支付
4. 用户完成支付后,微信回调后端通知接口
5. 后端验证签名并处理订单状态
```
#### API 端点
**POST `/api/h5/orders/:id/wechat-pay/jsapi`**
请求体:
```json
{
"open_id": "o6_bmjrPTlm6_2sgVt7hMZOPfL2M"
}
```
响应:
```json
{
"code": 0,
"msg": "支付订单创建成功",
"data": {
"prepay_id": "wx30123456789012345678901234567890",
"pay_config": {
"appId": "wxabcdef1234567890",
"timeStamp": "1706606400",
"nonceStr": "5K8264ILTKCH16CQ2502SI8ZNMTM67VS",
"package": "prepay_id=wx30123456789012345678901234567890",
"signType": "RSA",
"paySign": "oR9d8PuhnIc+YZ8cBHFCwfgpaK9gd..."
}
},
"timestamp": "2025-01-30T12:00:00Z"
}
```
#### 前端集成示例(微信内网页)
```javascript
// 1. 调用后端创建支付订单
const response = await fetch(`/api/h5/orders/${orderId}/wechat-pay/jsapi`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${token}`
},
body: JSON.stringify({
open_id: 'o6_bmjrPTlm6_2sgVt7hMZOPfL2M'
})
});
const result = await response.json();
// 2. 调用微信 JSAPI 唤起支付
if (result.code === 0) {
const payConfig = result.data.pay_config;
wx.chooseWXPay({
...payConfig,
success: function(res) {
// 支付成功,跳转到订单详情页
window.location.href = `/orders/${orderId}`;
},
fail: function(res) {
// 支付失败
alert('支付失败:' + res.err_msg);
}
});
}
```
### 微信 H5 支付
#### 业务流程
```
1. 前端调用后端创建 H5 支付订单
2. 后端调用微信支付接口,获取 H5 支付 URL
3. 前端跳转到 H5 支付 URL
4. 用户完成支付后,微信回调后端通知接口
5. 后端验证签名并处理订单状态
```
#### API 端点
**POST `/api/h5/orders/:id/wechat-pay/h5`**
请求体:
```json
{
"scene_info": {
"payer_client_ip": "123.12.12.123",
"h5_type": "Wap"
}
}
```
响应:
```json
{
"code": 0,
"msg": "H5 支付订单创建成功",
"data": {
"h5_url": "https://wx.tenpay.com/cgi-bin/mmpayweb-bin/checkmweb?prepay_id=wx..."
},
"timestamp": "2025-01-30T12:00:00Z"
}
```
#### 前端集成示例(浏览器)
```javascript
// 1. 调用后端创建 H5 支付订单
const response = await fetch(`/api/h5/orders/${orderId}/wechat-pay/h5`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${token}`
},
body: JSON.stringify({
scene_info: {
payer_client_ip: '123.12.12.123',
h5_type: 'Wap'
}
})
});
const result = await response.json();
// 2. 跳转到微信 H5 支付页面
if (result.code === 0) {
const returnUrl = encodeURIComponent(`https://your-domain.com/orders/${orderId}`);
window.location.href = `${result.data.h5_url}&redirect_url=${returnUrl}`;
}
```
### 支付回调处理
#### 回调端点
**POST `/api/callback/wechat-pay`**
该端点接收微信支付的异步通知。系统会自动:
1. 验证微信签名(使用商户证书)
2. 解密通知数据
3. 更新订单状态
4. 处理业务逻辑(分佣、钱包充值等)
5. 返回成功响应给微信
#### 回调处理流程
```
1. 微信发送 POST 请求到回调 URL
2. 系统验证请求签名PowerWeChat 自动处理)
3. 解析支付结果(交易状态、金额等)
4. 更新订单状态为"已支付"
5. 触发异步任务(分佣计算、套餐分配等)
6. 返回 200 OK 给微信(表示接收成功)
```
**注意**
- 回调接口必须在 **10秒内** 返回响应,否则微信会重试
- 系统已实现幂等性处理,重复通知不会重复处理
- 如果处理失败微信会重试多次最多3次
---
## 常见问题
### 1. 配置验证失败,服务启动失败
**错误日志**
```
FATAL: 微信配置不完整或无效
```
**解决方法**
- 检查所有必填环境变量是否设置
- 确认证书文件路径正确且文件存在
- 验证 APIv3 密钥是否为 32 位字符串
### 2. OAuth 授权失败,返回 1044 错误
**错误消息**
```json
{
"code": 1044,
"msg": "微信 OAuth 授权失败"
}
```
**可能原因**
- 授权码code已过期5分钟有效期
- 授权码已被使用过(一次性有效)
- AppID 或 AppSecret 配置错误
- 回调域名未在公众号后台配置
**解决方法**
- 重新发起授权流程获取新 code
- 检查公众号配置是否正确
- 查看 `logs/app.log` 获取详细错误信息
### 3. 支付订单创建失败,返回 1046 错误
**错误消息**
```json
{
"code": 1046,
"msg": "微信支付失败"
}
```
**可能原因**
- 商户号配置错误
- 证书文件无效或过期
- APIv3 密钥错误
- 订单金额为0或负数
**解决方法**
- 验证商户号和密钥是否正确
- 检查证书文件是否可读(权限问题)
- 确认证书序列号是否匹配
- 查看 `logs/app.log` 获取详细错误信息
### 4. 支付回调签名验证失败
**错误日志**
```
ERROR: 支付回调签名验证失败
```
**可能原因**
- 证书配置错误
- 证书序列号不匹配
- 证书已过期
**解决方法**
- 重新下载最新的商户证书
- 更新证书序列号配置
- 确保证书文件路径正确
### 5. 如何测试微信支付?
**开发环境测试**
1. 使用微信测试号(公众号测试账号)
2. 使用真实商户号的沙箱环境(需申请)
3. 使用 0.01 元测试订单(生产环境)
**注意**
- 测试订单需要真实支付
- 可以通过退款功能退回测试金额
- 建议使用沙箱环境进行测试
### 6. Redis 连接失败,影响微信功能吗?
**是的**,微信功能依赖 Redis 缓存 AccessToken。
**解决方法**
- 确保 Redis 服务正常运行
- 检查 Redis 连接配置(地址、端口、密码)
- 查看 `logs/app.log` 获取 Redis 连接错误
### 7. 如何调试微信支付问题?
**启用 HTTP 调试日志**
```bash
export JUNHONG_WECHAT_PAYMENT_HTTP_DEBUG=true
```
重启服务后,所有微信 API 请求和响应将记录到 `logs/app.log`
**查看日志**
```bash
tail -f logs/app.log | grep -i wechat
```
---
## 相关文档
- [微信公众号官方文档](https://developers.weixin.qq.com/doc/offiaccount/Getting_Started/Overview.html)
- [微信支付官方文档](https://pay.weixin.qq.com/wiki/doc/apiv3/index.shtml)
- [PowerWeChat SDK 文档](https://github.com/ArtisanCloud/PowerWeChat)
- [API 文档](./API文档.md)
- [环境变量配置](../environment-variables.md)

View File

@@ -0,0 +1,675 @@
# 微信集成功能验证指南
本文档提供微信公众号 OAuth 认证和微信支付功能的完整验证流程。
## 目录
- [前置准备](#前置准备)
- [配置验证](#配置验证)
- [功能测试](#功能测试)
- [1. 微信 OAuth 登录](#1-微信-oauth-登录)
- [2. 微信账号绑定](#2-微信账号绑定)
- [3. 微信 JSAPI 支付](#3-微信-jsapi-支付)
- [4. 微信 H5 支付](#4-微信-h5-支付)
- [5. 支付回调验证](#5-支付回调验证)
- [常见问题排查](#常见问题排查)
---
## 前置准备
### 1. 微信配置准备
确保已获取以下信息:
**公众号配置**
- [ ] AppID
- [ ] AppSecret
- [ ] OAuth 回调域名已配置(在公众号后台)
**支付配置**
- [ ] 商户号
- [ ] APIv3 密钥32位
- [ ] 商户证书文件apiclient_cert.pem
- [ ] 商户私钥文件apiclient_key.pem
- [ ] 证书序列号
- [ ] 支付回调 URL 已配置(在商户平台)
### 2. 环境准备
```bash
# 创建证书目录
mkdir -p /app/certs
# 复制证书文件
cp apiclient_cert.pem /app/certs/
cp apiclient_key.pem /app/certs/
# 设置文件权限
chmod 600 /app/certs/*
# 加载环境变量
source .env.local
```
### 3. 启动服务
```bash
# 编译并启动
go run cmd/api/main.go
# 或使用 Docker
docker-compose up -d api
```
---
## 配置验证
### 自动验证脚本
运行配置验证脚本:
```bash
# 加载环境变量
source .env.local
# 运行验证脚本
bash scripts/verify-wechat.sh
```
**预期输出**(所有检查通过):
```
========================================
微信配置验证脚本
========================================
1. 检查微信公众号配置
----------------------------------------
✓ JUNHONG_WECHAT_OFFICIAL_ACCOUNT_APP_ID
✓ JUNHONG_WECHAT_OFFICIAL_ACCOUNT_APP_SECRET
✓ JUNHONG_WECHAT_OFFICIAL_ACCOUNT_TOKEN
✓ JUNHONG_WECHAT_OFFICIAL_ACCOUNT_AES_KEY
✓ JUNHONG_WECHAT_OFFICIAL_ACCOUNT_OAUTH_REDIRECT_URL
2. 检查微信支付配置
----------------------------------------
✓ JUNHONG_WECHAT_PAYMENT_APP_ID
✓ JUNHONG_WECHAT_PAYMENT_MCH_ID
✓ JUNHONG_WECHAT_PAYMENT_API_V3_KEY
✓ JUNHONG_WECHAT_PAYMENT_CERT_PATH
✓ JUNHONG_WECHAT_PAYMENT_KEY_PATH
✓ JUNHONG_WECHAT_PAYMENT_SERIAL_NO
✓ JUNHONG_WECHAT_PAYMENT_NOTIFY_URL
3. 检查证书文件
----------------------------------------
✓ 文件存在: /app/certs/apiclient_cert.pem
✓ 文件存在: /app/certs/apiclient_key.pem
4. 验证配置格式
----------------------------------------
✓ 支付回调 URL 使用 HTTPS
5. 检查证书有效性(可选)
----------------------------------------
✓ 证书有效期至: Jan 30 12:00:00 2026 GMT
✓ 证书序列号匹配
========================================
验证结果
========================================
错误: 0
警告: 0
✅ 配置验证通过,所有配置正确
```
### 查看服务启动日志
```bash
# 查看实时日志
tail -f logs/app.log
# 或使用 Docker
docker logs -f junhong-api
```
**预期日志**(成功初始化):
```json
{
"level": "info",
"ts": "2025-01-30T12:00:00.000+0800",
"msg": "微信公众号服务初始化成功"
}
{
"level": "info",
"ts": "2025-01-30T12:00:00.001+0800",
"msg": "微信支付服务初始化成功"
}
{
"level": "info",
"ts": "2025-01-30T12:00:00.002+0800",
"msg": "服务启动成功",
"address": ":3000"
}
```
**错误日志**(配置问题):
```json
{
"level": "fatal",
"ts": "2025-01-30T12:00:00.000+0800",
"msg": "微信配置不完整或无效",
"error": "证书文件不存在: /app/certs/apiclient_cert.pem"
}
```
---
## 功能测试
### 1. 微信 OAuth 登录
#### 前端测试步骤
**步骤 1构造授权 URL**
```javascript
const appId = 'wxabcdef1234567890';
const redirectUri = encodeURIComponent('https://your-domain.com/wechat-callback');
const state = Math.random().toString(36).substring(7);
const authUrl = `https://open.weixin.qq.com/connect/oauth2/authorize?appid=${appId}&redirect_uri=${redirectUri}&response_type=code&scope=snsapi_userinfo&state=${state}#wechat_redirect`;
// 跳转到微信授权页面
window.location.href = authUrl;
```
**步骤 2处理回调**
在回调页面(`https://your-domain.com/wechat-callback`
```javascript
// 获取 URL 参数中的 code
const urlParams = new URLSearchParams(window.location.search);
const code = urlParams.get('code');
const state = urlParams.get('state');
if (!code) {
alert('授权失败:未获取到授权码');
return;
}
// 调用后端 OAuth 登录接口
fetch('https://api.your-domain.com/api/c/v1/wechat/auth', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ code })
})
.then(res => res.json())
.then(data => {
if (data.code === 0) {
console.log('登录成功', data.data);
localStorage.setItem('token', data.data.token);
// 跳转到主页
window.location.href = '/';
} else {
alert(`登录失败: ${data.msg}`);
}
})
.catch(err => {
console.error('请求失败', err);
alert('登录失败,请重试');
});
```
#### 后端日志验证
```bash
# 查看 OAuth 请求日志
tail -f logs/app.log | grep -i oauth
```
**成功日志**
```json
{
"level": "debug",
"ts": "2025-01-30T12:00:00.000+0800",
"msg": "微信 OAuth 授权成功",
"open_id": "o6_bmjrPTlm6_2sgVt7hMZOPfL2M",
"union_id": "oGfRjwX..."
}
{
"level": "info",
"ts": "2025-01-30T12:00:00.001+0800",
"msg": "个人客户创建成功",
"customer_id": 123
}
```
**失败日志**
```json
{
"level": "error",
"ts": "2025-01-30T12:00:00.000+0800",
"msg": "微信 OAuth 授权失败",
"code": "071abc123...",
"error": "invalid code"
}
```
#### 使用 curl 测试
```bash
# 替换为真实的授权码5分钟有效
CODE="071abc123456789def"
curl -X POST http://localhost:3000/api/c/v1/wechat/auth \
-H "Content-Type: application/json" \
-d "{\"code\":\"$CODE\"}"
```
**成功响应**
```json
{
"code": 0,
"msg": "登录成功",
"data": {
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"customer_id": 123,
"phone": "",
"nickname": "微信用户",
"is_new_user": true
},
"timestamp": "2025-01-30T12:00:00+08:00"
}
```
---
### 2. 微信账号绑定
#### 前提条件
- 已有个人客户账号
- 已获取 JWT Token
#### 测试步骤
```bash
# 替换为真实的 Token 和授权码
TOKEN="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
CODE="071abc123456789def"
curl -X POST http://localhost:3000/api/c/v1/bind-wechat \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d "{\"code\":\"$CODE\"}"
```
**成功响应**
```json
{
"code": 0,
"msg": "绑定成功",
"timestamp": "2025-01-30T12:00:00+08:00"
}
```
**失败响应**(微信号已被绑定):
```json
{
"code": 1020,
"msg": "该微信号已被其他账号绑定",
"timestamp": "2025-01-30T12:00:00+08:00"
}
```
---
### 3. 微信 JSAPI 支付
#### 前提条件
- 已创建订单(状态为"待支付"
- 在微信内网页中调用
- 已获取用户 OpenID
#### 测试步骤
**步骤 1创建支付订单**
```bash
# 替换为真实的 Token、订单ID 和 OpenID
H5_TOKEN="your_h5_token_here"
ORDER_ID=1
OPEN_ID="o6_bmjrPTlm6_2sgVt7hMZOPfL2M"
curl -X POST "http://localhost:3000/api/h5/orders/$ORDER_ID/wechat-pay/jsapi" \
-H "Authorization: Bearer $H5_TOKEN" \
-H "Content-Type: application/json" \
-d "{\"open_id\":\"$OPEN_ID\"}"
```
**成功响应**
```json
{
"code": 0,
"msg": "支付订单创建成功",
"data": {
"prepay_id": "wx30123456789012345678901234567890",
"pay_config": {
"appId": "wxabcdef1234567890",
"timeStamp": "1706606400",
"nonceStr": "5K8264ILTKCH16CQ2502SI8ZNMTM67VS",
"package": "prepay_id=wx30123456789012345678901234567890",
"signType": "RSA",
"paySign": "oR9d8PuhnIc+YZ8cBHFCwfgpaK9gd..."
}
},
"timestamp": "2025-01-30T12:00:00+08:00"
}
```
**步骤 2前端唤起支付**
```javascript
// 获取支付配置后,调用微信 JSAPI
wx.chooseWXPay({
...payConfig,
success: function(res) {
console.log('支付成功', res);
alert('支付成功');
// 跳转到订单详情页
window.location.href = `/orders/${orderId}`;
},
fail: function(res) {
console.error('支付失败', res);
alert('支付失败:' + res.err_msg);
}
});
```
#### 后端日志验证
```bash
# 查看支付请求日志
tail -f logs/app.log | grep -i jsapi
```
**成功日志**
```json
{
"level": "info",
"ts": "2025-01-30T12:00:00.000+0800",
"msg": "创建 JSAPI 支付订单成功",
"order_no": "ORDER_20250130_001",
"prepay_id": "wx30123456789012345678901234567890"
}
```
---
### 4. 微信 H5 支付
#### 前提条件
- 已创建订单(状态为"待支付"
- 在浏览器中调用(微信外)
#### 测试步骤
**步骤 1创建 H5 支付订单**
```bash
# 替换为真实的 Token 和订单ID
H5_TOKEN="your_h5_token_here"
ORDER_ID=1
curl -X POST "http://localhost:3000/api/h5/orders/$ORDER_ID/wechat-pay/h5" \
-H "Authorization: Bearer $H5_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"scene_info": {
"payer_client_ip": "123.12.12.123",
"h5_type": "Wap"
}
}'
```
**成功响应**
```json
{
"code": 0,
"msg": "H5 支付订单创建成功",
"data": {
"h5_url": "https://wx.tenpay.com/cgi-bin/mmpayweb-bin/checkmweb?prepay_id=wx30123456789012345678901234567890&package=3583359058"
},
"timestamp": "2025-01-30T12:00:00+08:00"
}
```
**步骤 2前端跳转支付**
```javascript
// 跳转到微信 H5 支付页面
const returnUrl = encodeURIComponent(`https://your-domain.com/orders/${orderId}`);
window.location.href = `${h5Url}&redirect_url=${returnUrl}`;
```
#### 后端日志验证
```bash
# 查看 H5 支付日志
tail -f logs/app.log | grep -i "h5"
```
**成功日志**
```json
{
"level": "info",
"ts": "2025-01-30T12:00:00.000+0800",
"msg": "创建 H5 支付订单成功",
"order_no": "ORDER_20250130_001",
"h5_url": "https://wx.tenpay.com/cgi-bin/mmpayweb-bin/checkmweb?..."
}
```
---
### 5. 支付回调验证
#### 验证方法1查看日志
支付成功后,微信会自动调用回调接口。查看日志验证:
```bash
# 查看支付回调日志
tail -f logs/app.log | grep -i "支付通知"
```
**成功日志**
```json
{
"level": "info",
"ts": "2025-01-30T12:00:00.000+0800",
"msg": "支付通知处理成功",
"out_trade_no": "ORDER_20250130_001",
"transaction_id": "4200001234202501301234567890"
}
```
#### 验证方法2查询订单状态
```bash
# 查询订单状态(使用数据库或 API
curl -X GET "http://localhost:3000/api/h5/orders/$ORDER_ID" \
-H "Authorization: Bearer $H5_TOKEN"
```
**预期响应**(订单已支付):
```json
{
"code": 0,
"data": {
"id": 1,
"order_no": "ORDER_20250130_001",
"status": "paid",
"total_amount": 100,
"paid_amount": 100,
"paid_at": "2025-01-30T12:00:00+08:00",
"payment_method": "wechat"
}
}
```
#### 验证方法3使用 Postman 模拟回调
**注意**:真实环境中由微信服务器调用,本地测试需要跳过签名验证。
```bash
# 模拟支付回调(仅测试环境)
curl -X POST http://localhost:3000/api/callback/wechat-pay \
-H "Content-Type: application/json" \
-d '{
"id": "test_id",
"create_time": "2025-01-30T12:00:00+08:00",
"resource_type": "encrypt-resource",
"event_type": "TRANSACTION.SUCCESS",
"summary": "支付成功",
"resource": {
"ciphertext": "...",
"nonce": "...",
"associated_data": "..."
}
}'
```
---
## 常见问题排查
### 1. 配置验证失败
**问题**:脚本报错 "缺失必填配置"
**解决方法**
```bash
# 检查环境变量是否加载
env | grep JUNHONG_WECHAT
# 重新加载环境变量
source .env.local
# 重新运行验证脚本
bash scripts/verify-wechat.sh
```
### 2. 服务启动失败
**问题**:日志显示 "微信配置不完整或无效"
**解决方法**
1. 查看详细错误日志
2. 检查证书文件路径是否正确
3. 验证证书文件权限600 或 644
4. 确认 APIv3 密钥长度为 32 位
### 3. OAuth 授权失败
**问题**:返回错误码 1044
**可能原因**
- 授权码已过期5分钟有效期
- 授权码已被使用过
- AppID 或 AppSecret 配置错误
- 回调域名未在公众号后台配置
**解决方法**
1. 重新发起授权流程获取新 code
2. 检查公众号配置
3. 查看详细日志:`tail -f logs/app.log | grep -i oauth`
### 4. 支付订单创建失败
**问题**:返回错误码 1046
**可能原因**
- 商户号配置错误
- 证书文件无效或过期
- APIv3 密钥错误
- 订单金额为0或负数
**解决方法**
1. 验证商户号和密钥
2. 检查证书有效期:`openssl x509 -in /app/certs/apiclient_cert.pem -noout -dates`
3. 确认证书序列号匹配
4. 查看详细日志:`tail -f logs/app.log | grep -i payment`
### 5. 支付回调签名验证失败
**问题**:日志显示 "支付回调签名验证失败"
**可能原因**
- 证书配置错误
- 证书序列号不匹配
- 证书已过期
**解决方法**
1. 重新下载最新的商户证书
2. 更新证书序列号配置
3. 确保证书文件路径正确
4. 验证证书:`bash scripts/verify-wechat.sh`
### 6. 启用调试日志
如需查看详细的 HTTP 请求日志:
```bash
# 设置环境变量
export JUNHONG_WECHAT_PAYMENT_HTTP_DEBUG=true
# 重启服务
go run cmd/api/main.go
# 查看调试日志
tail -f logs/app.log | grep -i wechat
```
---
## 验证清单
完成以下清单后,微信集成功能验证完成:
- [ ] 配置验证脚本通过0 错误)
- [ ] 服务启动成功,微信服务初始化日志正常
- [ ] 微信 OAuth 登录成功,返回 Token
- [ ] 微信账号绑定成功
- [ ] JSAPI 支付订单创建成功,返回支付配置
- [ ] H5 支付订单创建成功,返回支付 URL
- [ ] 支付回调处理成功,订单状态更新为"已支付"
- [ ] 日志中无错误或警告信息
---
## 相关文档
- [使用指南](./使用指南.md) - 详细的配置和部署说明
- [API 文档](./API文档.md) - 接口说明和示例
- [环境变量配置](../environment-variables.md) - 所有环境变量说明