# 需求09：C端支付方式限制配置化

> 状态：原需求独立稿；最终口径以标准评审稿为准。
> 依赖：[系统配置](../基础规范/系统配置.md)

---

## 背景

代码已经写好但被注释，注释原因是**微信支付参数未申请下来**，临时注释。

注释位置：
- `internal/handler/app/client_wallet.go`（钱包充值入口）
- `internal/service/client_order/service.go`（订单支付入口）

两处均有注释：`// 第三方支付方式与资产类型必须匹配：单卡只允许支付宝，设备只允许微信（已暂时注释）`

---

## 业务规则

| 资产类型 | 允许的第三方支付 | 禁止的第三方支付 |
|---------|----------------|----------------|
| IoT 卡  | 支付宝、钱包    | 微信           |
| 设备    | 微信、钱包      | 支付宝         |

**钱包支付对所有资产类型均允许。**

```mermaid
flowchart TD
    Pay[用户选择支付方式] --> Asset{资产类型}
    Asset -->|IoT卡| Card[读取卡允许方式]
    Asset -->|设备| Device[读取设备允许方式]
    Asset -->|未知| RejectUnknown[拒绝：资产类型无效]
    Card --> ConfigOK{配置可用?}
    Device --> ConfigOK
    ConfigOK -->|是| Match{支付方式在允许集合?}
    ConfigOK -->|否| SafeDefault[使用代码内安全默认集合]
    SafeDefault --> Match
    Match -->|是| Continue[继续支付]
    Match -->|否| Reject[拒绝并返回对应中文提示]
```

---

## 实现方案

### 1. 系统配置初始化（已在系统配置文档中定义）

```go
// tb_system_config 初始数据
config_key: "c2b.payment.card_allowed_methods"   → ["alipay","wallet"]
config_key: "c2b.payment.device_allowed_methods" → ["wechat","wallet"]
```

### 2. 恢复注释代码，改为读取配置

**文件**：`internal/service/client_order/service.go`

```go
// validatePaymentMethod 校验资产类型与支付方式是否匹配
func (s *Service) validatePaymentMethod(ctx context.Context, assetType string, paymentMethod string) error {
    // 钱包支付始终允许
    if paymentMethod == model.PaymentMethodWallet {
        return nil
    }

    var configKey string
    switch assetType {
    case model.AssetTypeIotCard: // "iot_card"，定义在 internal/model/asset_identifier.go
        configKey = "c2b.payment.card_allowed_methods"
    case model.AssetTypeDevice: // "device"，定义在 internal/model/asset_identifier.go
        configKey = "c2b.payment.device_allowed_methods"
    default:
        return errors.New(errors.CodeInvalidParam, "资产类型无效")
    }

    allowedMethods, err := sysconfig.GetStringSlice(ctx, configKey)
    if err != nil || len(allowedMethods) == 0 {
        // 配置异常时回退到代码内安全默认值，禁止放开全部支付方式。
        allowedMethods = defaultAllowedMethods(assetType)
        s.logger.Error("读取支付方式配置失败，已使用安全默认值",
            zap.String("config_key", configKey),
            zap.Error(err))
    }

    for _, m := range allowedMethods {
        if m == paymentMethod {
            return nil
        }
    }
    return errors.New(errors.CodeForbidden, "该资产类型不支持此支付方式")
}
```

在 `client_wallet.go`（充值）和 `client_order/service.go`（订单支付）的对应位置恢复调用。

系统配置更新时必须保证 `wallet` 始终存在于两个允许集合中；前端将钱包选项显示为勾选且不可取消，后端再次校验，避免配置破坏业务规则。

### 3. 错误信息

用户端错误提示（友好文案）：

```go
// 根据资产类型给出具体提示
switch assetType {
case model.AssetTypeIotCard:
    return errors.New(errors.CodeForbidden, "卡资产仅支持支付宝或余额支付")
case model.AssetTypeDevice:
    return errors.New(errors.CodeForbidden, "设备仅支持微信或余额支付")
}
```

---

## 前端对接

### C端支付页面

前端不维护另一份固定规则。资产初始化/详情接口返回后端已经计算好的：

```go
AllowedPaymentMethods []string `json:"allowed_payment_methods" description:"当前资产允许的支付方式"`
```

支付页只展示该集合中的方式，后端支付接口再次执行相同校验。配置变化后重新进入支付页或刷新资产信息即可获得新集合。

```
allowed_payment_methods = ["alipay", "wallet"] → 展示支付宝、余额
allowed_payment_methods = ["wechat", "wallet"] → 展示微信、余额
```

### 后台配置页面

在系统配置（系统设置 > 系统配置 > `c2b.payment` 模块）中，用 CheckboxGroup 展示：

```
卡资产允许支付方式：☑ 支付宝   ☑ 余额   ☐ 微信
设备允许支付方式：  ☐ 支付宝   ☑ 余额   ☑ 微信
```

余额选项固定勾选且禁用，不允许管理员取消。

修改后调用 `PUT /api/admin/system/config/c2b.payment.card_allowed_methods`。

> 注意：**微信支付参数申请下来后**，直接在系统配置里把对应资产类型勾上微信即可生效，无需改代码。
