impara/OrderAuditor
GitHub: impara/OrderAuditor
一个基于 Shopify 与 PostgreSQL 的重复订单检测应用,通过规则匹配自动标记并协助商家审核重复交易。
Stars: 0 | Forks: 0
# Duplicate Guard - Shopify 重复订单检测应用
## 概述
Duplicate Guard 是一个 Shopify 应用,它使用可配置的检测规则自动检测并标记来自同一客户的重复订单。它通过 Shopify webhook 处理订单,基于电子邮件、发货地址和其他标准分析重复项,并自动在 Shopify 中为商家审核标记可疑订单。
## 当前状态
**状态**: MVP 完成 & 生产就绪
**最后更新**: 2025年11月22日
### 已完成功能
- ✅ 支持订单创建事件的 Shopify webhook 监听端点(带 HMAC 验证)
- ✅ **通过 Shopify 管理 API 自动注册 webhook**
- ✅ 基于客户电子邮件、发货地址和可配置时间窗口的重复检测逻辑
- ✅ **检测设置的自动初始化** - webhook 首次触发时自动创建设置
- ✅ 通过 Shopify 管理 API 自动标记订单以标识重复项
- ✅ 仪表板展示已标记订单列表,显示客户信息、订单详情和重复匹配理由
- ✅ **实时仪表板更新** - 每 30 秒自动刷新以显示最新的已标记订单
- ✅ **订单详情弹窗** - 查看包含重复检测元数据、客户详情、发货地址以及直接链接到 Shopify 管理后台的完整订单信息
- ✅ **增强的客户名称提取** - 当客户字段不可用时回退到发货地址字段
- ✅ **订单解决系统** - 从仪表板忽略已标记订单,或在 Shopify 中移除标签时自动同步
- ✅ 统计卡片展示总已标记订单数、潜在重复价值以及最近活动指标
- ✅ 设置页面用于配置检测规则(时间窗口、匹配条件)和通知偏好
- ✅ PostgreSQL 数据库存储订单数据、检测规则和审计历史
- ✅ 分离路由、服务和存储层的 MVC 架构
## 本地开发环境搭建
### 前置条件
- Node.js(推荐 v20 或更高版本)
- PostgreSQL 数据库(本地或云托管)
- Shopify 商店并拥有管理访问权限
- npm 或 yarn 包管理器
### 步骤 1:克隆并安装依赖
```
# 导航到项目目录
cd duplicate-guard
# 安装依赖
npm install
```
**注意**:如果在安装过程中遇到 Windows/WSL 路径问题,请尝试:
- 在 WSL 环境中运行 npm(而非 Windows)
- 使用 `npm install --no-optional` 跳过可选依赖
- 确保使用的是 WSL 的 Node.js 安装,而非 Windows 的 Node.js
### 步骤 2:设置环境变量
1. 复制示例环境文件:
```
cp .env.example .env
```
2. 编辑 `.env` 并填写你的配置:
```
# 数据库配置
DATABASE_URL=postgresql://user:password@localhost:5432/duplicate-guard
# Shopify 配置
SHOPIFY_SHOP_DOMAIN=yourstore.myshopify.com
SHOPIFY_ACCESS_TOKEN=shpat_your_admin_api_access_token
SHOPIFY_WEBHOOK_SECRET=shpss_your_webhook_secret_key
# 应用程序配置
PORT=5000
APP_URL=http://localhost:5000
LOG_LEVEL=debug
# 客户端配置(可选)
VITE_SHOPIFY_SHOP_DOMAIN=yourstore.myshopify.com
```
#### 获取 Shopify 凭证
1. **在 Shopify 中创建自定义应用**:
- 进入 Shopify 管理后台 → 设置 → 应用和销售渠道 → 开发应用
- 点击“创建应用”
- 输入应用名称(例如:“Duplicate Guard”)
2. **配置 API 权限范围**:
- 点击“配置管理 API 权限”
- 启用以下权限范围:
- `read_orders` - 用于接收订单 webhook
- `write_orders` - 用于将订单标记为重复
- `read_customers` - **必需**,用于在 webhook 中访问客户电子邮件/名称
- 保存配置
3. **启用受保护的客户数据访问**(**至关重要**):
**⚠️ 没有此设置,webhook 中将无法获取客户数据(电子邮件、姓名、电话)!**
**⚠️ 计划要求**:访问客户 PII(电子邮件、姓名、电话)需要 **Shopify**、**高级** 或 **专业** 计划。**基础版/免费计划不提供此功能**。
- 在应用设置中,进入 **API 访问** → **受保护的客户数据访问**
- 点击 **管理**
- 在 **受保护的客户字段(可选)** 中选择:
- ✅ `email`
- ✅ `first_name`
- ✅ `last_name`
- ✅ `phone`
- 提供理由:“重复订单检测需要客户电子邮件和姓名来识别重复订单”
- 点击 **保存**
- **商家必须批准此请求** - 他们将在 Shopify 管理后台看到一条通知
**注意**:
- 在受保护客户数据访问获得批准之前,应用将显示“未知客户”和“unknown@example.com”
- 如果你在基础版/免费计划上,即使启用了受保护客户数据访问,客户数据也无法获取
- 开发商店可能有不同的限制,请检查你的 Shopify 计划
4. **安装应用**:
- 点击“安装应用”
- 复制 **管理 API 访问令牌**(以 `shpat_` 开头)
- 将其作为 `SHOPIFY_ACCESS_TOKEN` 使用
5. **获取 Webhook 密钥**:
- 在应用设置中,进入“API 凭证”
- 复制 **API 密钥**(以 `shpss_` 开头)
- 将其作为 `SHOPIFY_WEBHOOK_SECRET` 使用
6. **设置店铺域名**:
- 店铺域名格式为:`yourstore.myshopify.com`
- 将其作为 `SHOPIFY_SHOP_DOMAIN` 使用
### 步骤 3:设置数据库
#### 选项 A:使用 Docker Compose(推荐)
1. **使用 Docker Compose 启动 PostgreSQL**:
docker-compose up -d
这将启动一个 PostgreSQL 16 容器,包含:
- 数据库:`duplicate-guard`
- 用户:`duplicate-guard`
- 密码:`duplicate-guard`
- 端口:`5432`
2. **在 `.env` 中更新 DATABASE_URL**:
DATABASE_URL=postgresql://duplicate-guard:duplicate-guard@localhost:5432/duplicate-guard
3. **推送数据库架构**:
npm run db:push
这将创建必要的表:
- `orders` - 存储来自 Shopify webhook 的订单数据
- `detection_settings` - 重复检测规则配置
- `audit_logs` - 记录所有重复检测事件
**有用的 Docker 命令**:
- 停止数据库:`docker-compose down`
- 停止并移除数据:`docker-compose down -v`
- 查看日志:`docker-compose logs -f postgres`
- 重启:`docker-compose restart`
#### 选项 B:手动安装 PostgreSQL
1. **安装 PostgreSQL**(如果尚未安装):
# Ubuntu/Debian
sudo apt update && sudo apt install postgresql postgresql-contrib
# macOS(使用 Homebrew)
brew install postgresql@16
brew services start postgresql@16
2. **创建 PostgreSQL 数据库**:
# 使用 psql
createdb duplicate-guard
# 或使用 SQL
psql -U postgres
CREATE DATABASE "duplicate-guard";
3. **在 `.env` 中更新 DATABASE_URL**:
DATABASE_URL=postgresql://username:password@localhost:5432/duplicate-guard
4. **推送数据库架构**:
npm run db:push
### 步骤 4:设置 Webhook 测试(本地开发)
对于本地开发,需要将本地服务器暴露到互联网,以便 Shopify 可以发送 webhook。请选择以下选项之一:
#### 选项 A:使用 ngrok(推荐)
1. **安装 ngrok**:https://ngrok.com/download
2. **启动开发服务器**:
npm run dev
3. **在另一个终端中启动 ngrok**:
ngrok http 5000
4. **更新 `.env` 文件**,使用 ngrok URL:
APP_URL=https://your-ngrok-url.ngrok.io
5. **注册 webhook**(请参见步骤 5)
#### 选项 B:使用其他隧道服务
按照隧道服务的说明将端口 5000 暴露出来,然后在 `.env` 中更新 `APP_URL`。
### 步骤 5:注册 Shopify Webhook
本地服务器通过公共 URL 可访问后:
1. **启动开发服务器**:
npm run dev
2. **自动注册 webhook**:
curl -X POST http://localhost:5000/api/webhooks/register
或使用 webhook 状态端点检查注册状态:
curl http://localhost:5000/api/webhooks/status
3. **验证 webhook 注册**:
- 响应应显示 `orders/create` 和 `orders/updated` webhook 已成功注册
- 你也可以在 Shopify 管理后台 → 设置 → 通知 → Webhook 中检查
- 两个 webhook 都需要:`orders/create` 用于重复检测,`orders/updated` 用于自动解决
### 步骤 :运行应用
```
# 开发模式(热重载)
npm run dev
# 生产构建
npm run build
npm start
```
应用将在 `http://localhost:5000` 处可用
## 项目架构
### 技术栈
- **前端**:React + TypeScript,使用 Wouter 路由
- **UI 组件**:Shadcn UI(灵感来自 Shopify Polaris 设计)
- **样式**:Tailwind CSS 与 Inter 字体
- **后端**:Node.js + Express + TypeScript
- **数据库**:PostgreSQL 与 Drizzle ORM
- **外部 API**:Shopify 管理 API 用于订单标记
### 目录结构
```
├── client/
│ ├── src/
│ │ ├── components/ui/ # Reusable UI components (Shadcn)
│ │ ├── pages/ # Page components (Dashboard, Settings)
│ │ ├── lib/ # Utilities (React Query client, utils)
│ │ └── App.tsx # Main app with routing
├── server/
│ ├── services/ # Business logic services
│ │ ├── duplicate-detection.service.ts
│ │ └── shopify.service.ts
│ ├── db.ts # Database connection
│ ├── storage.ts # Data access layer
│ └── routes.ts # API routes
└── shared/
└── schema.ts # Shared TypeScript types and Drizzle schema
```
## API 端点
### 仪表板
- `GET /api/dashboard/stats` - 获取仪表板统计数据
- `GET /api/orders/flagged` - 获取已标记订单列表
- `POST /api/orders/:orderId/dismiss` - 忽略已标记订单(从列表移除并移除 Shopify 标签)
### 设置
- `GET /api/settings` - 获取检测设置(如果不存在则自动初始化)
- `PATCH /api/settings` - 更新检测设置
### Webhook 管理
- `GET /api/webhooks/status` - 检查 webhook 注册状态(显示 `orders/create` 和 `orders/updated`)
- `POST /api/webhooks/register` - 自动注册 `orders/create` 和 `orders/updated` webhook
### Webhook
- `POST /api/webhooks/shopify/orders/create` - Shopify 订单创建 webhook
- `POST /api/webhooks/shopify/orders/updated` - Shopify 订单更新 webhook(检测标签移除以实现自动解决)
## 重复检测逻辑
### 匹配标准
系统根据以下条件计算置信度分数(0-100 分):
- **电子邮件匹配**(50 分):相同的客户电子邮件
- **电话匹配**(50 分):相同的客户电话号码(支持格式差异标准化)
- **地址匹配**(45 或 25 分):相似的发货地址
- 完整匹配(街道 + 城市 + 邮编):45 分
- 部分匹配(街道 + 城市 或 街道 + 邮编):25 分
- 缺失地址自动跳过(数字产品无惩罚)
- **名称匹配**(20 分):相同的客户名称(不区分大小写,仅作证据支持)
### 计分策略
- **透明计分**:每种匹配类型使用固定分值,无隐藏加成或特殊情况
- **70 分阈值**:订单需达到 70 分或以上才会被标记为重复
- **自动处理**:缺失数据(如数字产品的地址)自动跳过
- **电话标准化**:电话号码会标准化以处理格式差异(例如 `+1234567890` 与 `(123) 456-7890`)
- **示例**:
- 电子邮件 + 名称:50 + 20 = 70 分 → 标记 ✓
- 电话 + 名称:50 + 20 = 70 分 → 标记 ✓
- 地址 + 名称:45 + 20 = 65 分 → 不标记(低于阈值)
- 仅电子邮件:50 分 → 不标记(需要名称匹配达到 70 分)
### 阈值
订单置信度 ≥ 70 分时会被标记为重复
## 订单解决与忽略
订单被标记为重复后,商家可通过两种方式解决:
### 手动忽略(仪表板)
1. 在仪表板中点击“查看详情”
2. 点击“忽略订单”按钮
3. 确认忽略对话框
4. 订单将从已标记列表移除,且 Shopify 中的 “Merge_Review_Candidate” 标签会被移除
### 自动解决(Shopify 管理后台)
1. 商家在 Shopify 管理后台直接移除 “Merge_Review_Candidate” 标签
2. 系统通过 `orders/updated` webhook 自动检测到标签移除
3. 订单自动解决并从已标记列表移除
4. 所有解决操作都会记录在审计日志中以便追溯
### 解决追踪
- 所有已解决订单在数据库中保留,`isFlagged: false`
- `resolvedAt` 时间戳记录解决时间
- `resolvedBy` 字段记录解决方式:`'manual_dashboard'` 或 `'shopify_tag_removed'`
- 审计日志记录所有忽略和解决事件,用于合规和统计分析
## 电子邮件通知
应用可在检测到重复订单时发送电子邮件通知。要启用此功能:
### 1. 配置 SMTP 设置
在 `.env` 文件中添加 SMTP 配置:
```
# SMTP 配置
SMTP_HOST=smtp.gmail.com
SMTP_PORT=587
SMTP_USER=your-email@gmail.com
SMTP_PASS=your-app-password
SMTP_FROM=your-email@gmail.com
```
### 2. 在设置中启用通知
1. 进入仪表板的设置页面
2. 启用“通知”开关
3. 输入接收通知的电子邮件地址
4. 可选调整通知阈值(默认:80% 置信度)
5. 保存设置
### 3. 常用 SMTP 提供商
**Gmail:**
- `SMTP_HOST=smtp.gmail.com`
- `SMTP_PORT=587`(TLS)或 `465`(SSL)
- 使用 [应用专用密码](https://support.google.com/accounts/answer/185833) 而非常规密码
- `SMTP_FROM` 应与 `SMTP_USER` 一致
**SendGrid:**
- `SMTP_HOST=smtp.sendgrid.net`
- `SMTP_PORT=587`
- `SMTP_USER=apikey`
- `SMTP_PASS=your-sendgrid-api-key`
- `SMTP_FROM=your-verified-sender@example.com`
**Mailgun:**
- `SMTP_HOST=smtp.mailgun.org`
- `SMTP_PORT=587`
- `SMTP_USER=postmaster@your-domain.mailgun.org`
- `SMTP_PASS=your-mailgun-password`
- `SMTP_FROM=noreply@your-domain.com`
**注意**:生产环境部署请参阅详细的 [生产部署指南](./docs/deployment/production.md) 以获取完整的 SMTP 配置说明。
## 环境变量参考
| 变量 | 必需 | 描述 | 示例 |
|------|------|------|------|
| `DATABASE_URL` | 是 | PostgreSQL 连接字符串 | `postgresql://user:pass@localhost:5432/db` |
| `SHOPIFY_API_KEY` | 是 | Shopify 客户端 ID(合作伙伴应用) | `your_client_id` |
| `SHOPIFY_API_SECRET` | 是 | Shopify 客户端密钥(合作伙伴应用) | `your_client_secret` |
| `SHOPIFY_WEBHOOK_SECRET` | 否\*\*\* | 旧版 webhook 密钥(自定义应用) | `shpss_...`(旧版,合作伙伴应用不需要) |
| `PORT` | 否 | 服务器端口(默认:5000) | `5000` |
| `APP_URL` | 是\* | webhook 注册的公共 URL | `http://localhost:5000` 或 `https://your-domain.com` |
| `LOG_LEVEL` | 否 | 日志详细程度(默认:debug) | `error`、`warn`、`info`、`debug` |
| `SMTP_HOST` | 否\*\* | SMTP 服务器主机名 | `smtp.gmail.com` |
| `SMTP_PORT` | 否\*\* | SMTP 服务器端口 | `587` |
| `SMTP_USER` | 否\*\* | SMTP 认证用户名 | `your-email@gmail.com` |
| `SMTP_PASS` | 否\*\* | SMTP 认证密码 | `your-app-password` |
| `SMTP_FROM` | 否\*\* | 发件人邮箱地址 | `your-email@gmail.com` |
\* webhook 注册必需。使用 ngrok 或类似隧道服务进行本地开发。
\*\* 必需用于电子邮件通知。参见 [电子邮件通知](#email-notifications) 章节。
\*\*\*** 对于合作伙伴应用(多租户嵌入式应用):`SHOPIFY_WEBHOOK_SECRET` **不需要**。应用使用 `SHOPIFY_API_SECRET`(客户端密钥)进行 webhook 验证。`SHOPIFY_WEBHOOK_SECRET` 仅用于旧版自定义应用,应移除。
### Shopify 凭证映射
**对于合作伙伴应用(当前设置):**
- **客户端 ID** → `SHOPIFY_API_KEY` 和 `VITE_SHOPIFY_API_KEY`
- **客户端密钥** → `SHOPIFY_API_SECRET(用于 OAuth 和 webhook 验证)
- `SHOPIFY_WEBHOOK_SECRET` → **不需要**(旧版,可移除)
**对于旧版自定义应用(已弃用):**
- `SHOPIFY_WEBHOOK_SECRET` 曾是独立的“API 密钥”
- 这已不再用于合作伙伴应用
## 故障排除
### 数据库连接问题
- 确认 PostgreSQL 正在运行:`pg_isready`
- 检查 `.env` 中的连接字符串格式
- 确保数据库存在:`psql -l | grep duplicate-guard`
### Webhook 未接收事件
- 确认 webhook 已注册:`GET /api/webhooks/status`
- 检查 `APP_URL` 是否公开可访问
- 对于合作伙伴应用:确认 `SHOPIFY_API_SECRET` 与应用客户端密钥匹配
- 对于旧版自定义应用:确认 `SHOPIFY_WEBHOOK_SECRET` 与应用凭证匹配
- 检查服务器日志中的 HMAC 验证错误
- 检查服务器日志中的 webhook 验证错误和配置问题
### npm 安装问题(Windows/WSL)
- 确保在 WSL 环境中使用 Node.js,而非 Windows
- 尝试:`npm install --no-optional`
- 清除 npm 缓存:`npm cache clean --force`
- 重新安装:`rm -rf node_modules package-lock.json && npm install`
## 脚本
- `npm run dev` - 启动开发服务器(热重载)
- `npm run build` - 构建生产版本
- `npm start` - 启动生产服务器
- `npm run check` - TypeScript 类型检查
- `npm run db:push` - 推送数据库架构变更(开发环境)
- `npm run db:generate` - 生成迁移文件(可选,用于版本控制)
## 设计系统
### 颜色
- **主色**:绿色(#008060)- Shopify 品牌色,用于 CTA
- **危险色**:红色 - 关键告警和高置信度重复
- **图表色-4**:琥珀色 - 中等置信度警告
### 字体
- **字体**:Inter(Shopify Polaris 标准)
- **页面标题**:20px 半粗
- **节标题**:16px 半粗
- **正文文本**:14px 常规
- **数据标签**:12px 中等
- **统计数字**:24px 粗体
## 已知限制
- **仅地址匹配**:仅地址匹配(45 分)+ 名称(20 分)= 65 分,低于 70 分阈值。需启用电子邮件或电话匹配才能生效。
- **仅电子邮件/电话匹配**:仅电子邮件或电话(50 分)需要名称匹配(20 分)才能达到 70 分。仅含电子邮件/电话的订单不会被标记。
- 单检测设置配置文件(暂不支持多店铺)
- 仪表板平均解决时间目前为占位值
## 计划增强功能
- 批量操作用于审核和忽略已标记订单
- ~~检测到重复时发送电子邮件/Slack 通知~~ ✅ 已实现
- 详细的订单对比视图,显示并排重复分析
- 分析仪表板,展示趋势、模式、欺诈风险评分、解决指标和投资回报率
- OAuth 流程用于多店铺 Shopify 应用分发
## 生产部署
使用 Docker 进行生产部署时,请参阅详细的 [生产部署指南](./docs/deployment/production.md) 以获取完整说明,包括:
- 服务器设置与 Docker 安装
- 环境配置
- 数据库初始化
- Webhook 注册
- 维护与故障排除
- 备份流程
## 许可证
MIT
标签:30 秒自动刷新, HMAC 签名验证, MITM代理, MVC 架构, PostgreSQL 数据库, SEO:Shopify 插件, SEO:订单去重, SEO:防重复下单, Shopify 应用, Webhook 监听, 商户风控, 地址匹配, 实时仪表盘, 审计日志, 客户信息提取, 时间窗口检测, 测试用例, 电商平台安全, 自动初始化, 自动化攻击, 自动标签, 计价值分析, 订单去重, 订单审核, 订单详情弹窗, 请求拦截, 通知偏好, 邮箱匹配, 配置化规则, 重复订单检测