diff --git a/openspec/changes/add-payment-configuration-management/design.md b/openspec/changes/add-payment-configuration-management/design.md new file mode 100644 index 0000000..5ad6b18 --- /dev/null +++ b/openspec/changes/add-payment-configuration-management/design.md @@ -0,0 +1,68 @@ +# Design: 支付配置管理能力对齐 MVP 文档 + +## Context + +`docs/mvp/支付配置.md` 已经给出了后台支付配置的接口文档,包括: + +- 支付配置列表查询 +- 支付配置创建 +- 支付配置详情 +- 支付配置更新 +- 当前生效配置查询 + +但当前仓库没有对应的 OpenSpec 能力说明,现有前端代码也仍沿用 `wechat-config` 命名,容易把“微信配置页面”误读成“仅支持微信支付的页面”。 + +同时,MVP 文档里存在一个需要明确记录的歧义: + +- `provider_type` 只列出了 `wechat`、`wechat_v2`、`fuiou` +- 字段模型中又出现了完整的 `ali_*` 支付宝字段组 + +如果不先明确这层假设,后续实现时很容易在 `provider_type` 是否应支持 `alipay` 上出现分叉。 + +## Goals / Non-Goals + +- Goals: + - 为支付配置能力建立统一的 OpenSpec 能力边界 + - 把列表、创建、详情、更新和当前生效配置查询的契约写清楚 + - 明确敏感字段脱敏和“已配置/未配置”状态展示语义 +- Non-Goals: + - 不在本次 spec 中补充文档未描述的删除接口 + - 不在本次 spec 中补充文档未描述的激活/停用写接口 + - 不擅自把 `provider_type` 扩展为文档未枚举的值 + +## Decisions + +- Decision: 能力名称使用 `payment-configuration-management`,而不是沿用页面路径中的 `wechat-config` + - Rationale: 该能力已经覆盖微信、小程序、公众号、支付宝和富友相关字段,业务边界明显大于“微信配置”。 + +- Decision: `provider_type` 在本次 spec 中严格按文档限定为 `wechat`、`wechat_v2`、`fuiou` + - Rationale: 这是文档明确枚举出的渠道主类型,spec 不应凭猜测新增 `alipay`。 + +- Decision: `ali_*` 字段在本次 spec 中被视为支付配置对象支持的可选扩展字段,但不单独引入新的 `provider_type` + - Rationale: 文档已经明确给出这些字段;在没有更正枚举之前,先把它们作为统一字段模型的一部分记录下来。 + +- Decision: 所有读取响应中的敏感信息都必须采用脱敏或配置状态标记,不能返回明文密钥 + - Rationale: 文档的详情和当前生效配置示例已经给出了 `***`、`已配置`、`未配置` 这类语义。 + +- Decision: 更新接口采用部分更新语义,只要求提交实际需要修改的字段 + - Rationale: 文档明确说明更新请求“仅传需要修改的字段”,spec 需要保留这个约束。 + +## Risks / Trade-offs + +- 风险: 当前前端类型定义尚未覆盖 `ali_*` 字段,按 spec 对齐后会触发类型和页面联动修改。 + - Mitigation: 实施时先调整 `src/types/api/wechatConfig.ts`,再逐步修改列表、表单和详情展示。 + +- 风险: 文档没有给出“如何切换当前生效配置”的写接口,只有 `is_active` 状态和 `/active` 查询接口。 + - Mitigation: 本次 spec 仅约束读取和编辑契约,不推断未文档化的激活写流程。 + +## Migration Plan + +1. 先补齐支付配置类型定义和 API 契约。 +2. 再对齐设置页列表、创建/编辑表单和详情页展示。 +3. 最后核对首页“当前生效支付配置”展示组件是否遵循相同的脱敏语义。 +4. 通过文档示例验证列表、详情、更新和当前生效配置的典型返回结构。 + +## Open Questions + +- `provider_type` 是否最终应该补充 `alipay`,使其与 `ali_*` 字段组形成一致的一对一渠道语义? +- 删除、激活、停用接口是否属于正式 MVP 范围,是否需要在后续文档和 spec 中补齐? diff --git a/openspec/changes/add-payment-configuration-management/proposal.md b/openspec/changes/add-payment-configuration-management/proposal.md new file mode 100644 index 0000000..1b16a9b --- /dev/null +++ b/openspec/changes/add-payment-configuration-management/proposal.md @@ -0,0 +1,32 @@ +# Change: 新增支付配置管理规范 + +## Why + +`docs/mvp/支付配置.md` 已经定义了后台支付配置的 MVP 接口契约,但当前仓库缺少对应的 OpenSpec 能力说明。 + +现有前端实现仍以 `wechat-config` 命名该能力,且字段范围与读取语义和文档并不完全一致,例如文档包含统一的 `ali_*` 字段组、敏感字段脱敏规则和“当前生效配置”查询语义。如果不先把能力范围和契约写入 spec,后续 API、类型和页面实现会继续失同步。 + +## What Changes + +- 新增 `payment-configuration-management` capability,覆盖支付配置列表、创建、详情、更新和当前生效配置查询。 +- 定义统一的支付配置字段模型: + - 基础字段:`id`、`name`、`description`、`provider_type`、`is_active`、`created_at`、`updated_at` + - 渠道扩展字段:`wx_*`、`miniapp_*`、`oa_*`、`ali_*`、`fy_*` +- 定义读取场景下的敏感字段脱敏规则和“已配置/未配置”状态展示语义。 +- 明确 `provider_type` 枚举、分页筛选参数,以及更新接口按需提交字段的语义。 + +## Impact + +- Affected specs: + - `payment-configuration-management` +- Affected code: + - `src/api/modules/wechatConfig.ts` + - `src/types/api/wechatConfig.ts` + - `src/views/settings/wechat-config/index.vue` + - `src/views/settings/wechat-config/detail.vue` + - `src/views/dashboard/analysis/widget/ActiveWechatConfig.vue` +- Dependencies: + - `docs/mvp/支付配置.md` + - 后端 `/api/admin/wechat-configs` 相关接口契约 +- Breaking changes: + - 不引入新的路由级破坏性变更,但前端现有 `wechat-config` 类型与展示可能需要补齐文档字段和脱敏语义 diff --git a/openspec/changes/add-payment-configuration-management/specs/payment-configuration-management/spec.md b/openspec/changes/add-payment-configuration-management/specs/payment-configuration-management/spec.md new file mode 100644 index 0000000..4d59778 --- /dev/null +++ b/openspec/changes/add-payment-configuration-management/specs/payment-configuration-management/spec.md @@ -0,0 +1,123 @@ +# Payment Configuration Management Specification + +## ADDED Requirements + +### Requirement: 支付渠道类型定义 + +系统 SHALL 使用统一的 `provider_type` 枚举标识支付渠道主类型。 + +#### Scenario: 支持文档定义的渠道枚举 + +- **WHEN** 系统接收或返回支付配置对象的 `provider_type` +- **THEN** 该值 MUST 为 `wechat`、`wechat_v2` 或 `fuiou` 之一 + +### Requirement: 支付配置字段模型 + +系统 SHALL 在支付配置的读取和写入契约中支持统一的字段模型。 + +#### Scenario: 支持基础字段和渠道扩展字段 + +- **WHEN** 系统处理支付配置对象 +- **THEN** 对象 MUST 支持基础字段 `id`、`name`、`description`、`provider_type`、`is_active`、`created_at`、`updated_at` +- **AND** 对象 MUST 支持微信支付字段 `wx_mch_id`、`wx_api_v2_key`、`wx_api_v3_key`、`wx_serial_no`、`wx_notify_url`、`wx_cert_content`、`wx_key_content` +- **AND** 对象 MUST 支持小程序字段 `miniapp_app_id`、`miniapp_app_secret` +- **AND** 对象 MUST 支持公众号字段 `oa_app_id`、`oa_app_secret`、`oa_token`、`oa_aes_key`、`oa_oauth_redirect_url` +- **AND** 对象 MUST 支持支付宝字段 `ali_app_id`、`ali_private_key`、`ali_public_key`、`ali_notify_url`、`ali_return_url`、`ali_pay_expire_minutes`、`ali_production` +- **AND** 对象 MUST 支持富友字段 `fy_api_url`、`fy_ins_cd`、`fy_mchnt_cd`、`fy_term_id`、`fy_notify_url`、`fy_private_key`、`fy_public_key` + +### Requirement: 支付配置列表查询 + +系统 SHALL 提供支付配置列表查询能力,支持分页和条件筛选。 + +#### Scenario: 查询分页支付配置列表 + +- **WHEN** 管理员请求 `/api/admin/wechat-configs?page=1&page_size=20` +- **THEN** 系统 MUST 返回包含 `code`、`msg`、`timestamp` 和 `data` 的响应 +- **AND** `data` MUST 包含 `items`、`page`、`size`、`total` +- **AND** `page` MUST 从 `1` 开始 +- **AND** `page_size` MUST 支持 `1-100` 范围内的请求值 +- **AND** `items` 中的每个配置对象 MUST 至少包含 `id`、`name`、`description`、`provider_type`、`is_active`、`created_at`、`updated_at` + +#### Scenario: 按支付渠道类型筛选 + +- **WHEN** 管理员使用 `provider_type=wechat_v2` 查询支付配置列表 +- **THEN** 系统 MUST 只返回 `provider_type` 为 `wechat_v2` 的配置 + +#### Scenario: 按激活状态筛选 + +- **WHEN** 管理员使用 `is_active=true` 查询支付配置列表 +- **THEN** 系统 MUST 只返回当前已激活的支付配置 + +### Requirement: 创建支付配置 + +系统 SHALL 允许管理员创建支付配置,并保存与所选渠道相关的字段。 + +#### Scenario: 创建微信直连支付配置 + +- **GIVEN** 管理员提交的请求体包含 `name`、`provider_type=wechat`、`wx_mch_id`、`wx_api_v3_key`、`wx_serial_no`、`wx_cert_content`、`wx_key_content` +- **WHEN** 管理员向 `/api/admin/wechat-configs` 发起创建请求 +- **THEN** 系统 MUST 创建新的支付配置记录 +- **AND** 响应 MUST 返回新建后的支付配置对象 +- **AND** 系统 MUST 同时保存请求中提供的可选 `description`、`miniapp_*`、`oa_*`、`ali_*`、`fy_*` 字段 + +#### Scenario: 缺少基础必填字段时拒绝创建 + +- **WHEN** 创建请求缺少 `name` 或 `provider_type` +- **THEN** 系统 MUST 拒绝该请求 + +#### Scenario: 缺少微信直连必填字段时拒绝创建 + +- **WHEN** 创建请求的 `provider_type=wechat` 且缺少文档要求的微信直连必填字段 +- **THEN** 系统 MUST 拒绝该请求 + +### Requirement: 获取支付配置详情 + +系统 SHALL 提供按 ID 查询单个支付配置详情的能力。 + +#### Scenario: 返回完整的支付配置详情 + +- **WHEN** 管理员请求 `/api/admin/wechat-configs/{id}` +- **THEN** 系统 MUST 返回单个支付配置对象 +- **AND** 返回对象 MUST 包含基础字段和所有已支持的渠道扩展字段 + +### Requirement: 更新支付配置 + +系统 SHALL 支持按 ID 对支付配置进行部分更新。 + +#### Scenario: 仅更新提交的字段 + +- **WHEN** 管理员向 `/api/admin/wechat-configs/{id}` 提交只包含 `name` 的更新请求 +- **THEN** 系统 MUST 只更新已提交的字段 +- **AND** 系统 MUST 保留未提交字段的原值 + +#### Scenario: 更新微信密钥和证书字段 + +- **WHEN** 管理员提交包含 `wx_api_v3_key`、`wx_cert_content`、`wx_key_content` 的更新请求 +- **THEN** 系统 MUST 更新目标支付配置对应的字段值 + +### Requirement: 获取当前生效支付配置 + +系统 SHALL 提供当前生效支付配置查询能力。 + +#### Scenario: 返回当前生效的支付配置 + +- **WHEN** 管理员请求 `/api/admin/wechat-configs/active` +- **THEN** 系统 MUST 返回当前生效的支付配置对象 +- **AND** 返回对象中的 `is_active` MUST 等于 `true` +- **AND** 返回对象 MUST 遵循与详情接口一致的字段模型 + +### Requirement: 敏感字段脱敏与配置状态展示 + +系统 SHALL 在读取支付配置时保护敏感信息,并使用配置状态标记展示证书和密钥内容。 + +#### Scenario: 读取响应中的密钥字段必须脱敏 + +- **WHEN** 系统返回支付配置的读取响应(列表、详情或当前生效配置) +- **THEN** `miniapp_app_secret`、`oa_app_secret`、`oa_token`、`oa_aes_key`、`wx_api_v2_key`、`wx_api_v3_key` MUST 以脱敏值返回 +- **AND** 系统 MUST NOT 在读取响应中返回这些字段的明文值 + +#### Scenario: 证书和私钥字段以配置状态表示 + +- **WHEN** 系统返回支付配置中的证书或私钥相关字段 +- **THEN** `wx_cert_content`、`wx_key_content`、`ali_private_key`、`ali_public_key`、`fy_private_key`、`fy_public_key` MUST 以配置状态值返回 +- **AND** 该状态值 MUST 至少能够区分 `已配置` 和 `未配置` diff --git a/openspec/changes/add-payment-configuration-management/tasks.md b/openspec/changes/add-payment-configuration-management/tasks.md new file mode 100644 index 0000000..e8b44e6 --- /dev/null +++ b/openspec/changes/add-payment-configuration-management/tasks.md @@ -0,0 +1,21 @@ +# Implementation Tasks + +## 1. 契约与类型对齐 + +- [x] 1.1 对齐 `src/types/api/wechatConfig.ts` 的请求与响应类型,补齐 `ali_*` 字段、统一字段模型和分页响应字段。 +- [x] 1.2 校准 `src/api/modules/wechatConfig.ts` 对列表、详情、创建、更新和当前生效配置接口的调用契约。 + +## 2. 设置页能力对齐 + +- [x] 2.1 更新 `src/views/settings/wechat-config/index.vue` 的列表筛选、分页和列展示,覆盖文档中的 `provider_type`、`is_active` 和渠道关键字段。 +- [x] 2.2 更新创建/编辑表单,支持文档定义的微信、小程序、公众号、支付宝和富友字段及其校验规则。 +- [x] 2.3 更新 `src/views/settings/wechat-config/detail.vue`,按 spec 展示完整字段分组、脱敏信息和配置状态。 + +## 3. 当前生效配置展示 + +- [x] 3.1 校准 `src/views/dashboard/analysis/widget/ActiveWechatConfig.vue` 对当前生效配置接口及脱敏字段的展示语义。 + +## 4. 验证 + +- [ ] 4.1 联调并验证列表、详情、创建、更新和当前生效配置查询的典型场景。 +- [x] 4.2 运行 `cmd /c openspec validate add-payment-configuration-management --strict`。 diff --git a/src/api/modules/wechatConfig.ts b/src/api/modules/wechatConfig.ts index c0549b7..cfc2e62 100644 --- a/src/api/modules/wechatConfig.ts +++ b/src/api/modules/wechatConfig.ts @@ -1,5 +1,5 @@ /** - * 微信支付配置管理 API + * 支付配置管理 API */ import { BaseService } from '../BaseService' @@ -12,6 +12,8 @@ import type { UpdateWechatConfigRequest } from '@/types/api/wechatConfig' +const PAYMENT_CONFIG_BASE_URL = '/api/admin/wechat-configs' + export class WechatConfigService extends BaseService { /** * 获取支付配置列表 @@ -19,21 +21,21 @@ export class WechatConfigService extends BaseService { static getWechatConfigs( params?: WechatConfigQueryParams ): Promise> { - return this.get>('/api/admin/wechat-configs', params) + return this.get>(PAYMENT_CONFIG_BASE_URL, params) } /** * 获取支付配置详情 */ static getWechatConfigById(id: number): Promise> { - return this.get>(`/api/admin/wechat-configs/${id}`) + return this.get>(`${PAYMENT_CONFIG_BASE_URL}/${id}`) } /** * 创建支付配置 */ static createWechatConfig(data: CreateWechatConfigRequest): Promise> { - return this.post>('/api/admin/wechat-configs', data) + return this.post>(PAYMENT_CONFIG_BASE_URL, data) } /** @@ -43,34 +45,34 @@ export class WechatConfigService extends BaseService { id: number, data: UpdateWechatConfigRequest ): Promise> { - return this.put>(`/api/admin/wechat-configs/${id}`, data) + return this.put>(`${PAYMENT_CONFIG_BASE_URL}/${id}`, data) } /** * 删除支付配置 */ static deleteWechatConfig(id: number): Promise> { - return this.delete>(`/api/admin/wechat-configs/${id}`) + return this.delete>(`${PAYMENT_CONFIG_BASE_URL}/${id}`) } /** * 激活支付配置 */ static activateWechatConfig(id: number): Promise> { - return this.post>(`/api/admin/wechat-configs/${id}/activate`) + return this.post>(`${PAYMENT_CONFIG_BASE_URL}/${id}/activate`) } /** * 停用支付配置 */ static deactivateWechatConfig(id: number): Promise> { - return this.post>(`/api/admin/wechat-configs/${id}/deactivate`) + return this.post>(`${PAYMENT_CONFIG_BASE_URL}/${id}/deactivate`) } /** * 获取当前生效的支付配置 */ static getActiveWechatConfig(): Promise> { - return this.get>('/api/admin/wechat-configs/active') + return this.get>(`${PAYMENT_CONFIG_BASE_URL}/active`) } } diff --git a/src/types/api/wechatConfig.ts b/src/types/api/wechatConfig.ts index 67295d9..ffaa857 100644 --- a/src/types/api/wechatConfig.ts +++ b/src/types/api/wechatConfig.ts @@ -5,26 +5,30 @@ // 支付渠道类型 export type PaymentProviderType = 'wechat' | 'wechat_v2' | 'fuiou' -// 微信配置 -export interface WechatConfig { +interface PaymentConfigBase { id: number name: string description: string provider_type: PaymentProviderType is_active: boolean + created_at: string + updated_at: string +} - // 小程序配置 +interface MiniappConfigFields { miniapp_app_id: string miniapp_app_secret: string +} - // 公众号配置 +interface OfficialAccountConfigFields { oa_app_id: string oa_app_secret: string oa_token: string oa_aes_key: string oa_oauth_redirect_url: string +} - // 微信配置 +interface WechatPayConfigFields { wx_mch_id: string wx_api_v2_key: string wx_api_v3_key: string @@ -32,8 +36,19 @@ export interface WechatConfig { wx_serial_no: string wx_cert_content: string wx_key_content: string +} - // 富友支付配置 +interface AlipayConfigFields { + ali_app_id: string + ali_private_key: string + ali_public_key: string + ali_notify_url: string + ali_return_url: string + ali_pay_expire_minutes: number + ali_production: boolean +} + +interface FuiouConfigFields { fy_api_url: string fy_ins_cd: string fy_mchnt_cd: string @@ -41,11 +56,25 @@ export interface WechatConfig { fy_notify_url: string fy_private_key: string fy_public_key: string - - created_at: string - updated_at: string } +type PaymentConfigWritableFields = Partial< + MiniappConfigFields & + OfficialAccountConfigFields & + WechatPayConfigFields & + AlipayConfigFields & + FuiouConfigFields +> + +// 支付配置 +export interface WechatConfig + extends PaymentConfigBase, + MiniappConfigFields, + OfficialAccountConfigFields, + WechatPayConfigFields, + AlipayConfigFields, + FuiouConfigFields {} + // 查询参数 export interface WechatConfigQueryParams { page?: number @@ -58,78 +87,21 @@ export interface WechatConfigQueryParams { export interface WechatConfigListResponse { items: WechatConfig[] page: number - page_size: number + size: number total: number + page_size?: number } // 创建微信支付配置请求 -export interface CreateWechatConfigRequest { +export interface CreateWechatConfigRequest extends PaymentConfigWritableFields { name: string provider_type: PaymentProviderType description?: string - - // 小程序配置 - miniapp_app_id?: string - miniapp_app_secret?: string - - // 公众号配置 - oa_app_id?: string - oa_app_secret?: string - oa_token?: string - oa_aes_key?: string - oa_oauth_redirect_url?: string - - // 微信配置 - wx_mch_id?: string - wx_api_v2_key?: string - wx_api_v3_key?: string - wx_notify_url?: string - wx_serial_no?: string - wx_cert_content?: string - wx_key_content?: string - - // 富友支付配置 - fy_api_url?: string - fy_ins_cd?: string - fy_mchnt_cd?: string - fy_term_id?: string - fy_notify_url?: string - fy_private_key?: string - fy_public_key?: string } // 更新微信支付配置请求 -export interface UpdateWechatConfigRequest { +export interface UpdateWechatConfigRequest extends PaymentConfigWritableFields { name?: string description?: string provider_type?: PaymentProviderType - - // 小程序配置 - miniapp_app_id?: string - miniapp_app_secret?: string - - // 公众号配置 - oa_app_id?: string - oa_app_secret?: string - oa_token?: string - oa_aes_key?: string - oa_oauth_redirect_url?: string - - // 微信配置 - wx_mch_id?: string - wx_api_v2_key?: string - wx_api_v3_key?: string - wx_notify_url?: string - wx_serial_no?: string - wx_cert_content?: string - wx_key_content?: string - - // 富友支付配置 - fy_api_url?: string - fy_ins_cd?: string - fy_mchnt_cd?: string - fy_term_id?: string - fy_notify_url?: string - fy_private_key?: string - fy_public_key?: string } diff --git a/src/utils/business/paymentConfig.ts b/src/utils/business/paymentConfig.ts new file mode 100644 index 0000000..7318b09 --- /dev/null +++ b/src/utils/business/paymentConfig.ts @@ -0,0 +1,64 @@ +import type { PaymentProviderType, WechatConfig } from '@/types/api' + +const PAYMENT_PROVIDER_LABELS: Record = { + wechat: '微信直连', + wechat_v2: '微信直连V2', + fuiou: '富友支付' +} + +export const getPaymentProviderText = (type: PaymentProviderType): string => { + return PAYMENT_PROVIDER_LABELS[type] || type +} + +export const getPaymentMerchantId = ( + config: Pick +): string => { + if (config.provider_type === 'wechat' || config.provider_type === 'wechat_v2') { + return config.wx_mch_id || '-' + } + + if (config.provider_type === 'fuiou') { + return config.fy_mchnt_cd || '-' + } + + return '-' +} + +export const getPaymentNotifyUrl = ( + config: Pick +): string => { + if (config.provider_type === 'wechat' || config.provider_type === 'wechat_v2') { + return config.wx_notify_url || '-' + } + + if (config.provider_type === 'fuiou') { + return config.fy_notify_url || '-' + } + + return config.ali_notify_url || '-' +} + +export const getPaymentConfigStatus = (value: string | undefined | null): string => { + if (!value) return '未配置' + + if (value === 'configured' || value === '[已配置]' || value === '已配置') { + return '已配置' + } + + if (value === '[未配置]' || value === '未配置') { + return '未配置' + } + + return value +} + +export const isMaskedPaymentConfigValue = (value: string | undefined | null): boolean => { + if (!value) return false + + return ( + value === '***' || + value.includes('已配置') || + value.includes('未配置') || + /\*{2,}/.test(value) + ) +} diff --git a/src/views/dashboard/analysis/widget/ActiveWechatConfig.vue b/src/views/dashboard/analysis/widget/ActiveWechatConfig.vue index 567af56..6cb4295 100644 --- a/src/views/dashboard/analysis/widget/ActiveWechatConfig.vue +++ b/src/views/dashboard/analysis/widget/ActiveWechatConfig.vue @@ -8,7 +8,7 @@ 未配置
- 创建于 {{ formatDateTime(activeConfig.created_at) }} + 更新于 {{ formatDateTime(activeConfig.updated_at) }}
@@ -61,6 +61,30 @@
{{ activeConfig.is_active ? '已激活' : '未激活' }}
+
+
+ 🔔 +
+
+
支付回调地址
+
{{ getNotifyUrl(activeConfig) }}
+
+
+
+
+ 🔐 +
+
+
凭证状态
+
{{ getCredentialStatus(activeConfig) }}
+
+
@@ -72,8 +96,14 @@