跳到主要内容

CGRateS 操作和充值行为

本指南解释了 CGRateS 操作在 OmniCRM 中的工作原理,特别关注余额管理、充值行为以及不同操作类型如何影响附加产品。

概述

在 OmniCRM 的在线计费系统 (CGRateS) 中,操作 是在客户账户上添加、修改或删除余额的机制。

当您提供附加或充值产品时,实际上是在执行一个 CGRateS 操作,该操作会操纵账户的余额。(您也可以采用其他方法,例如通过 Playbooks 手动向账户添加余额 - 这只是我们用来保持整洁的常见模式)

关键概念

操作 - 在账户上执行的一组操作(添加余额、扣除余额、记录 CDR 等)

余额 - 一种资源(数据、语音、短信、货币)的数量,具有到期时间和权重

余额 ID - 余额类型的唯一标识符(例如,“数据包”、“语音分钟”)

权重 - 余额消耗的优先级(先消耗权重较高的)

到期时间 - 余额到期的时间(绝对日期或相对时间,如“+5 天”)

阻止器 - 一种特殊的余额标志,当余额达到零时阻止所有使用,即使存在其他余额(见 余额阻止器

关键:操作必须先定义

在您可以使用 ExecuteAction 在 Playbook 中执行操作之前,该操作必须已经在 CGRateS 中定义。这是一个常常被忽视的关键前提。

操作何时定义

操作通常在 初始系统配置产品设置 期间定义,而不是在提供服务时。它们通常通过 Python 脚本创建,同时配置 CRM 和 OCS。

操作如何链接到产品

操作通过命名约定与产品链接:

  • CGRateS 操作: ActionsId = "Action_50gb-data-pack"
  • CRM 产品: product_slug = "50gb-data-pack"
  • 在 Playbook 中: cgr_action_name = "Action_" + product_slug

当 Playbook 运行时,它从 product_slug 构建操作名称并调用 ExecuteAction。如果该操作在 CGRateS 中不存在,则提供失败。

在哪里定义操作

操作应在您的产品配置脚本中定义,通常在:

  1. 初始设置期间 - 在首次配置系统时
  2. 创建新产品时 - 在创建产品之前定义操作
  3. 通过配置脚本 - 配置 OCS 和 CRM 的 Python 脚本

示例:在创建产品之前定义操作

import cgrateshttpapi

OCS_Obj = cgrateshttpapi.CGRateS("ocs.example.com", "2080")
tenant = "your_tenant"

# 步骤 1:首先在 CGRateS 中定义操作
Action_50GB_Data_Pack = {
"method": "ApierV1.SetActions",
"params": [{
"ActionsId": "Action_50gb-data-pack",
"Tenant": tenant,
"Actions": [
{
"Identifier": "*topup",
"BalanceType": "*data",
"Units": 50 * 1024 * 1024 * 1024,
"ExpiryTime": "+720h",
"Weight": 10
}
]
}]
}

result = OCS_Obj.SendData(Action_50GB_Data_Pack)
assert result['error'] is None or result['error'] == "EXISTS"

# 步骤 2:现在在 CRM 中创建产品
# (product_slug = "50gb-data-pack" 将链接到 Action_50gb-data-pack)

如果操作不存在会发生什么:

# 在 Playbook 中
- name: 执行操作
uri:
url: "http://{{ crm_config.ocs.cgrates }}/jsonrpc"
method: POST
body:
{
"method": "APIerSv1.ExecuteAction",
"params": [{
"ActionsId": "Action_50gb-data-pack"
}]
}
register: response

# 如果操作未定义的结果:
# response.json.error = "SERVER_ERROR: 未找到操作"
# 提供失败,客户未获得余额

有关定义操作和将其链接到产品的完整详细信息,请参见 定义产品

余额独立性

默认情况下,附加产品创建 独立余额,这些余额彼此独立工作。这意味着:

  • 您可以同时拥有多个活动附加产品
  • 每个附加产品维护自己的余额和到期时间
  • 余额根据权重和到期规则进行消耗

示例:多个独立附加产品

# 客户拥有这些活动余额:
余额 1:
ID: "Data_5GB_5days_uuid_abc123"
类型: *data
: 5368709120 # 5GB(字节)
到期: 2024-12-29
权重: 10

余额 2:
ID: "Data_10GB_30days_uuid_def456"
类型: *data
: 10737418240 # 10GB(字节)
到期: 2025-01-24
权重: 10

# 两个余额独立共存
# 系统根据权重消耗;如果权重相等,则不保证顺序

当余额具有相同的权重并匹配相同的目的地时,CGRateS 不保证基于到期的消耗顺序 - 顺序取决于余额的存储和检索方式。使用不同的权重来控制消耗顺序

操作类型: *topup 与 *topup_reset

操作类型���定了新余额如何与相同 ID 的现有余额交互。

*topup - 加法行为

*topup 操作 添加到现有余额,并 延长到期时间

行为:

  • 查找具有匹配 ID 的现有余额
  • 将新值添加到现有值
  • 更新到期为新的到期时间
  • 保留现有余额(滚存)

示例:

# 初始状态:
余额:
ID: "Data_Package__5368709120"
: 1073741824 # 剩余 1GB
到期: 2024-12-24(剩余 1 天)

# 运行 *topup 操作:
操作:
标识符: "*topup"
余额:
ID: "Data_Package__5368709120" # 相同 ID - 触发滚存
: 5368709120 # 5GB
ExpiryTime: "+5d"

# *topup 后的结果:
余额:
ID: "Data_Package__5368709120"
: 6442450944 # 6GB(1GB + 5GB 滚存)
OriginalValue: 5368709120 # 仍然显示原始 5GB
Value_hr: "6 GB"
OriginalValue_hr: "5 GB"
Remaining_hr: "6 GB of 5 GB (1 GB rolled over)"
到期: 2024-12-29(从现在起 5 天)

用例:

  • 忠诚奖励(向现有套餐添加额外数据)
  • 补偿信用(向客户余额添加)
  • 滚存数据包
  • 宽限期延长

*topup_reset - 重置行为

*topup_reset 操作 替换现有余额,具有相同的余额 ID。

行为:

  • 查找具有匹配 ID 的现有余额
  • 丢弃旧值(��滚存)
  • 将余额设置为新值
  • 更新到期为新的到期时间

示例:

# 初始状态:
余额:
ID: "Data_Package__5368709120"
: 1073741824 # 剩余 1GB
到期: 2024-12-24(剩余 1 天)

# 运行 *topup_reset 操作:
操作:
标识符: "*topup_reset"
余额:
ID: "Data_Package__5368709120" # 相同 ID - 触发重置
: 5368709120 # 5GB
ExpiryTime: "+5d"

# *topup_reset 后的结果:
余额:
ID: "Data_Package__5368709120"
: 5368709120 # 5GB(旧的 1GB 被丢弃)
OriginalValue: 5368709120
Value_hr: "5 GB"
Remaining_hr: "5 GB of 5 GB"
PercentUsed: 0
到期: 2024-12-29(从现在起 5 天)

用例:

  • 每月定期套餐(每月重置为全额)
  • 固定大小的充值(始终获得确切金额)
  • 计划变更(用新计划余额替换旧计划)
  • 防止滥用(无法堆叠无限附加产品)

使用余额 ID 控制余额行为

余额 ID 对于确定余额是独立的还是相互交互至关重要。

余额 ID 命名约定和可读视图

OmniCRM 使用特定的余额 ID 命名约定,编码描述性名称和原始大小。这允许 API 自动生成用于 Web UI 的可读字段。

余额 ID 模式:

{描述性名称}__{原始大小(基本单位)}

示例余额 ID:

# 数据余额:100GB
"AU_Data_Domestic__107374182400"
# 分解为:
# - 描述部分:"AU_Data_Domestic"(它是什么 - 类型/目的地)
# - 分隔符:"__"(双下划线)
# - 原始大小:"107374182400"(100GB 以字节为单位)
# - UI 显示:"AU Data Domestic - 100 GB"

# 语音余额:3000分钟
"AU_Voice_Domestic__180000000000000"
# - 描述部分:"AU_Voice_Domestic"(而不是 "AU_Voice_Domestic_3000min")
# - 原始大小:"180000000000000"(3000分钟以纳秒为单位)
# - UI 显示:"AU Voice Domestic - 3000 min"

# 短信余额:3000条消息
"AU_SMS_Domestic__3000"
# - 描述部分:"AU_SMS_Domestic"
# - 原始大小:"3000"(计数)
# - UI 显示:"AU SMS Domestic - 3000 msgs"

重要: 不要在描述部分中包含大小信息 - 这是多余的,因为大小在 __ 后编码,API 会自动将其转换为可读格式。

API 如何创建可读视图:

当 OmniCRM API 从 CGRateS 检索余额数据时,它会自动解析余额 ID 并生成 _hr(人类可读)字段:

{
"BalanceMap": {
"*data": [
{
"ID": "AU_Data_Domestic__107374182400",
"Value": 53687091200,
"ExpiryTime": "2025-01-25T23:59:59Z",
"Weight": 1200,

// 自动生成的人类可读字段:
"ID_hr": "AU Data Domestic",
"OriginalValue": 107374182400,
"OriginalValue_hr": "100 GB",
"Value_hr": "50 GB",
"Remaining_hr": "50 GB of 100 GB",
"PercentUsed": 50,
"ExpiryTime_hr": "2025年1月25日(22天)"
}
]
}
}

API 处理逻辑:

  1. 解析余额 ID:

    balance_id = "AU_Data_Domestic__107374182400"
    parts = balance_id.split("__")

    descriptive_name = parts[0] # "AU_Data_Domestic"
    original_size = int(parts[1]) if len(parts) > 1 else None # 107374182400
  2. 生成可读的描述性名称:

    # 用空格替换下划线
    id_hr = descriptive_name.replace("_", " ") # "AU Data Domestic"
  3. 将原始大小转换为人类单位:

    # 对于数据余额(字节)
    if balance_type == "*data":
    original_value_hr = convert_bytes_to_gb(original_size) # "100 GB"

    # 对于语音余额(纳秒)
    elif balance_type == "*voice":
    original_value_hr = convert_ns_to_minutes(original_size) # "3000 min"

    # 对于短信余额(计数)
    elif balance_type == "*sms":
    original_value_hr = f"{original_size} msgs" # "3000 msgs"
  4. 计算使用百分比:

    if original_size and original_size > 0:
    percent_used = ((original_size - current_value) / original_size) * 100
  5. 格式化剩余显示:

    remaining_hr = f"{current_value_hr} of {original_value_hr}"
    # "50 GB of 100 GB"

Web UI 显示:

前端使用这些 _hr 字段来显示用户友好的余额信息:

// 而不是显示原始值:
// ID: "AU_Data_Domestic__107374182400"
// 值: 53687091200

// 显示人类可读:
<BalanceCard>
<Title>{balance.ID_hr}</Title> {/* "AU Data Domestic" */}
<Progress value={balance.PercentUsed}> {/* 50% */}
{balance.Remaining_hr} {/* "50 GB of 100 GB" */}
</Progress>
<Expiry>{balance.ExpiryTime_hr}</Expiry> {/* "2025年1月25日(22天)" */}
</BalanceCard>

为什么这很重要:

  1. 原始大小跟踪 - 即使余额部分消耗,UI 也可以显示 "50 GB of 100 GB",而不仅仅是 "50 GB 剩余"

  2. 进度可视化 - 百分比计算使准确的进度条成为可能

  3. 一致的命名 - 从余额 ID 中提取的描述性名称确保后端和前端之间的一致性

  4. 滚存显示 - 当使用 *topup(滚存)时,如果客户有 70 GB 剩余并充值 100 GB:

    • 余额 ID 保持不变:"AU_Data_Domestic__107374182400"(原始 100 GB)
    • 当前值变为:170 GB
    • UI 显示: "170 GB (70 GB rolled over + 100 GB new)"

最佳实践 - 创建余额 ID:

始终在 __ 后包含原始大小,以便正确的 UI 显示。不要在描述名称中重复大小信息:

# 好 - 描述名称 + 基本单位中的大小
Action_Data_100GB = {
"Actions": [
{
"BalanceId": f"AU_Data_Domestic__{100 * 1024 * 1024 * 1024}",
"Units": 100 * 1024 * 1024 * 1024
}
]
}

# 坏 - 描述名称中冗余大小
Action_Data_100GB = {
"Actions": [
{
"BalanceId": f"AU_Data_Domestic_100GB__{100 * 1024 * 1024 * 1024}", # 冗余!
"Units": 100 * 1024 * 1024 * 1024
}
]
}

# 坏 - 没有大小信息(UI 无法计算百分比)
Action_Data_100GB = {
"Actions": [
{
"BalanceId": "AU_Data_Domestic", # 缺少 __size
"Units": 100 * 1024 * 1024 * 1024
}
]
}

特殊情况 - 货币余额:

货币余额通常不包括原始大小,因为它们可以充值到任何金额:

# 没有大小编码的货币余额
{
"BalanceId": "PAYG_Monetary_Balance",
"BalanceType": "*monetary",
"Units": 5000 # $50.00
}

# UI 仅显示当前余额,而不显示百分比
# "余额:$50.00"

策略 1:唯一 ID(独立余额)

使用唯一 ID(例如,带有 UUID)创建完全独立的余额,这些余额永远不会相互交互。

示例实现:

- name: 生成唯一余额标识符
set_fact:
uuid: "{{ 99999999 | random | to_uuid }}"
balance_id: "Data_5days__5368709120_{{ uuid[0:8] }}"

- name: 使用 *topup 添加独立余额
uri:
url: "http://{{ crm_config.ocs.cgrates }}/jsonrpc"
method: POST
body_format: json
body:
{
"method": "ApierV1.AddBalance",
"params": [{
"Tenant": "{{ crm_config.ocs.ocsTenant }}",
"Account": "{{ service_uuid }}",
"BalanceType": "*data",
"Balance": {
"ID": "{{ balance_id }}",
"Value": 5368709120,
"ExpiryTime": "+5d",
"Weight": 10
}
}]
}

结果: 每个附加产品即使客户多次购买相同的附加产品,也会创建一个新的、独立的余额。

# 客户购买 "5 天数据附加产品" 三次:
余额 1:
ID: "Data_5days__5368709120_a1b2c3d4"
: 5368709120
Value_hr: "5 GB"
OriginalValue_hr: "5 GB"
Remaining_hr: "5 GB of 5 GB"
到期: 2024-12-29

余额 2:
ID: "Data_5days__5368709120_e5f6g7h8"
: 5368709120
Value_hr: "5 GB"
Remaining_hr: "5 GB of 5 GB"
到期: 2024-12-30

余额 3:
ID: "Data_5days__5368709120_i9j0k1l2"
: 5368709120
Value_hr: "5 GB"
Remaining_hr: "5 GB of 5 GB"
到期: 2024-12-31

# 总可用:15GB,分为三个独立余额
# 每个在 UI 中显示为 "Data 5days - 5 GB of 5 GB"

策略 2:共享 ID 与 *topup(滚存)

使用相同的余额 ID 和 *topup 操作,允许余额滚存和到期延长。

示例实现:

- name: 设置固定余额 ID
set_fact:
balance_id: "Data_5days__5368709120"

- name: 使用 *topup 添加余额(滚存)
uri:
url: "http://{{ crm_config.ocs.cgrates }}/jsonrpc"
method: POST
body_format: json
body:
{
"method": "ApierV1.AddBalance",
"params": [{
"Tenant": "{{ crm_config.ocs.ocsTenant }}",
"Account": "{{ service_uuid }}",
"BalanceType": "*data",
"Balance": {
"ID": "Data_5days__5368709120",
"Value": 5368709120,
"ExpiryTime": "+5d",
"Weight": 10
}
}]
}

结果: 后续购买将添加到现有余额并延长到期。

# 第 1 天:客户购买 "5 天数据附加产品":
余额:
ID: "Data_5days__5368709120"
: 5368709120
Value_hr: "5 GB"
Remaining_hr: "5 GB of 5 GB"
PercentUsed: 0
到期: 2024-12-29

# 第 3 天:客户使用 1GB,然后再次购买相同的附加产品:
余额:
ID: "Data_5days__5368709120"
: 9663676416 # 4GB 剩余 + 5GB 新增
Value_hr: "9 GB"
OriginalValue_hr: "5 GB"
Remaining_hr: "9 GB (4 GB rolled over + 5 GB new)"
PercentUsed: -80 # 负值表示滚存
到期: 2024-12-27(从今天起 +5 天)

策略 3:共享 ID 与 *topup_reset(固定金额)

使用相同的余额 ID 和 *topup_reset 操作,始终重置为固定金额。

示例实现:

- name: 设置固定余额 ID
set_fact:
balance_id: "Monthly_Plan__32212254720"

- name: 使用 *topup_reset 添加余额(无滚存)
uri:
url: "http://{{ crm_config.ocs.cgrates }}/jsonrpc"
method: POST
body_format: json
body:
{
"method": "ApierV1.SetActions",
"params": [{
"ActionsId": "Action_Reset_Monthly_Plan",
"Actions": [{
"Identifier": "*topup_reset",
"BalanceType": "*data",
"Units": 32212254720, # 30GB
"ExpiryTime": "*monthly",
"DestinationIds": "*any",
"BalanceId": "Monthly_Plan__32212254720",
"Weight": 10
}]
}]
}

结果: 每个月,余额重置为确切的 30GB,无论使用了多少。

# 第 1 个月,第 1 天:
余额:
ID: "Monthly_Plan__32212254720"
: 32212254720
Value_hr: "30 GB"
Remaining_hr: "30 GB of 30 GB"
PercentUsed: 0
到期: 2024-12-31

# 第 1 个月,第 25 天:客户使用了 28GB
余额:
ID: "Monthly_Plan__32212254720"
: 2147483648
Value_hr: "2 GB"
Remaining_hr: "2 GB of 30 GB"
PercentUsed: 93
到期: 2024-12-31

# 第 2 个月,第 1 天:ActionPlan 运行 *topup_reset
余额:
ID: "Monthly_Plan__32212254720"
: 32212254720 # 重置为全额,未使用的 2GB 丢失
Value_hr: "30 GB"
Remaining_hr: "30 GB of 30 GB"
PercentUsed: 0
到期: 2025-01-31

余额阻止器

余额阻止器 是 CGRateS 的一个强大功能,允许您 阻止或限制使用,即使存在余额。当余额达到零时,阻止器余额停止消费,阻止进一步使用,无论是否存在其他可用余额。

阻止器的工作原理

当余额具��� Blocker: true 时:

  1. 当阻止器有值时 → 允许使用,直到阻止器金额
  2. 当阻止器达到零时 → 所有使用停止,即使存在其他余额
  3. 返回错误INSUFFICIENT_CREDIT_BALANCE_BLOCKER 阻止会话

关键特征:

  • 阻止器余额即使在值为零时也会被 检查(与正常余额不同,正常余额会被跳过)
  • 当遇到具有剩余使用请求的阻止器时,CGRateS 停止处理 并返回错误
  • 阻止器适用于所有余额类型:*voice*data*sms*monetary

阻止器的用例

1. 账户暂停

当账户暂停时阻止所有使用(例如,付款失败):

Action_Suspend_Account = {
"id": "0",
"method": "ApierV1.SetActions",
"params": [{
"ActionsId": "Action_suspend-account",
"Overwrite": True,
"Tenant": tenant,
"Actions": [
# 添加零值阻止器以防止所有使用
{
"Identifier": "*topup",
"BalanceId": "Suspension_Blocker",
"BalanceType": "*monetary",
"DestinationIDs": "*any",
"Units": 0, # 零值
"BalanceWeight": 9999, # 最高优先级 - 首先检查
"Blocker": True, # 阻止所有使用
"Weight": 10
}
]
}]
}

result = OCS_Obj.SendData(Action_Suspend_Account)

结果: 所有通话/数据/SMS 被阻止,无论其他余额如何。

2. 支出限制

限制最大支出以防止账单冲击:

Action_Monthly_Plan_With_Cap = {
"id": "0",
"method": "ApierV1.SetActions",
"params": [{
"ActionsId": "Action_monthly-with-cap",
"Overwrite": True,
"Tenant": tenant,
"Actions": [
# 10GB 包含数据
{
"Identifier": "*topup_reset",
"BalanceId": f"Included_Data__{10 * 1024 * 1024 * 1024}",
"BalanceType": "*data",
"DestinationIDs": "Dest_PLMN_OnNet",
"Units": 10 * 1024 * 1024 * 1024,
"ExpiryTime": "*month",
"BalanceWeight": 1200, # 首先消耗
"Weight": 95
},
# $50 超支限制(阻止器)
{
"Identifier": "*topup_reset",
"BalanceId": "Overage_Cap",
"BalanceType": "*monetary",
"DestinationIDs": "*any",
"Units": 5000, # $50.00 最大超支
"ExpiryTime": "*month",
"BalanceWeight": 1000, # 在包含后消耗
"Blocker": True, # 当 $50 花费达到时停止
"Weight": 90
}
]
}]
}

流程:

  1. 客户使用 10GB 包含 → 免费(来自 Included_Data_10GB)
  2. 客户使用额外的 5GB → 从 Overage_Cap 收费(按 PAYG 费率)
  3. 当 Overage_Cap 达到 $0 → 所有使用被阻止(达到支出限制)

3. 限时免费试用

为试用账户提供有限的免费使用:

Action_Trial_Account = {
"id": "0",
"method": "ApierV1.SetActions",
"params": [{
"ActionsId": "Action_trial-100-minutes",
"Overwrite": True,
"Tenant": tenant,
"Actions": [
# 100 免费分钟(阻止器 - 耗尽时停止)
{
"Identifier": "*topup",
"BalanceId": f"Trial_Voice__{100 * 60 * 1000000000}",
"BalanceType": "*voice",
"DestinationIDs": "Dest_Domestic_All",
"Units": 100 * 60 * 1000000000, # 100分钟
"ExpiryTime": "+720h", # 30天
"BalanceWeight": 1200,
"Blocker": True, # 用完 100 分钟后不再使用
"Weight": 10
}
]
}]
}

结果: 客户获得 100 分钟免费。之后,所有通话被阻止(没有自动收费)。

4. 特定目的地阻止

在允许其他目的地的同时阻止特定目的地:

Action_Block_Premium_Numbers = {
"id": "0",
"method": "ApierV1.SetActions",
"params": [{
"ActionsId": "Action_block-premium",
"Overwrite": True,
"Tenant": tenant,
"Actions": [
# 允许常规使用
{
"Identifier": "*topup_reset",
"BalanceId": "Regular_Usage",
"BalanceType": "*monetary",
"DestinationIDs": "*any",
"Units": 10000, # $100
"ExpiryTime": "*month",
"BalanceWeight": 1000,
"Weight": 20
},
# 阻止高级号码(0900 等)
{
"Identifier": "*topup",
"BalanceId": "Premium_Blocker",
"BalanceType": "*monetary",
"DestinationIDs": "Dest_Domestic_Premium",
"Units": 0, # 零值
"BalanceWeight": 2000, # 更高权重 - 首先检查高级
"Blocker": True,
"Weight": 10
}
]
}]
}

流程:

  • 国内通话 → 使用 Regular_Usage 余额
  • 高级号码通话 → 匹配 Premium_Blocker(权重 2000 > 1000) → 被阻止

阻止器与禁用余额的区别

不要将 BlockerDisabled 混淆:

特征阻止器禁用
目的当余额耗尽时停止使用暂时暂停余额
当值 > 0余额正常可用余额被跳过/忽略
当值 = 0阻止所有进一步使用余额被跳过(尝试下一个余额)
用例支出限制、上限、试用限制暂时暂停特定余额
# 阻止器:允许 10GB,然后阻止所有使用
{
"BalanceId": f"Data_Cap__{10 * 1024 * 1024 * 1024}",
"Units": 10 * 1024 * 1024 * 1024,
"Blocker": True # 在 10GB 时停止使用
}

# 禁用:完全忽略此余额(暂时暂停)
{
"BalanceId": f"Bonus_Data__{5 * 1024 * 1024 * 1024}",
"Units": 5 * 1024 * 1024 * 1024,
"Disabled": True # 此余额将不会被使用
}

实际示例:混合计划与安全上限

结合单元余额、货币溢出和阻止器上限:

Action_Safe_Hybrid_Plan = {
"id": "0",
"method": "ApierV1.SetActions",
"params": [{
"ActionsId": "Action_safe-hybrid-plan",
"Overwrite": True,
"Tenant": tenant,
"Actions": [
{
"Identifier": "*reset_account",
"Weight": 700
},
# 500 国内分钟包含
{
"Identifier": "*topup_reset",
"BalanceId": f"Domestic_Voice__{500 * 60 * 1000000000}",
"BalanceType": "*voice",
"DestinationIDs": "Dest_Domestic_All",
"Units": 500 * 60 * 1000000000,
"ExpiryTime": "*month",
"BalanceWeight": 1200,
"Weight": 95
},
# $20 超支津贴
{
"Identifier": "*topup_reset",
"BalanceId": "Overage_Allowance",
"BalanceType": "*monetary",
"DestinationIDs": "*any",
"Units": 2000, # $20.00
"ExpiryTime": "*month",
"BalanceWeight": 1000,
"Weight": 90
},
# $50 硬限制(阻止器)
{
"Identifier": "*topup_reset",
"BalanceId": "Hard_Spending_Cap",
"BalanceType": "*monetary",
"DestinationIDs": "*any",
"Units": 5000, # $50.00 绝对最大
"ExpiryTime": "*month",
"BalanceWeight": 500, # 低于超支 - 最后使用
"Blocker": True, # 达到上限时停止
"Weight": 85
}
]
}]
}

客户旅程:

  1. 0-500分钟: 使用 Domestic_Voice_500min(免费)
  2. 500-700分钟: 使用 Overage_Allowance,每分钟 $0.10 = $20(200分钟)
  3. 700-1200分钟: 使用 Hard_Spending_Cap,每分钟 $0.10 = $50(500分钟)
  4. 在1200分钟时: Hard_Spending_Cap 耗尽 → 所有使用被阻止

客户获得总共 1200 分钟,超支最大为 $50。

最佳实践与阻止器

  1. 为阻止器使用高权重

    "BalanceWeight": 9999  # 确保阻止器首先被检查
  2. 零值阻止器以实现立即阻止

    "Units": 0,  # 立即阻止
    "Blocker": True
  3. 在阻止器耗尽之前通知客户

    • 使用 ActionTriggers 在 80%、90%、100% 阻止器使用时发送通知
    • 在阻止发生之前给客户增加上限的选项
  4. 在取消暂停时移除阻止器

    # 使用 *remove_balance 删除阻��器
    {
    "Identifier": "*remove_balance",
    "BalanceId": "Suspension_Blocker"
    }
  5. 测试阻止器行为

    • 验证阻止器返回 INSUFFICIENT_CREDIT_BALANCE_BLOCKER 错误
    • 确认 CDR 显示成本 = -1.0 当被阻止时
    • 测试在阻止器耗尽后其他余额不被使用

故障排除阻止器

问题:尽管阻止器为零,但使用未被阻止

可能原因:

  1. 阻止器权重太低(其他余额首先检查)
  2. DestinationIDs 不匹配使用目的地
  3. 阻止器字段未设置为 True

解决方案:

# 验证阻止器配置
OCS_Obj.SendData({
'method': 'ApierV2.GetAccount',
'params': [{"Tenant": tenant, "Account": "service_uuid"}]
})

# 检查:
# - Blocker: true
# - BalanceWeight 是最高的(例如,9999)
# - DestinationIDs 包括使用目的地
# - 值 = 0

问题:意外阻止使用

可能原因: 不小心创建了低值的阻止器余额

解决方案: 检查所有余额是否有 Blocker: true,并验证其值是否适合您的用例。

余额消耗规则

规则 1:目的地精度优先

具有 更高目的地精度(更具体的目的地匹配)的余额首先被消耗。这是由前缀匹配长度决定的。

# 客户拨打 +44-20-1234-5678(伦敦,英国)

余额 1:
DestinationIDs: "Dest_UK_London" # 前缀:"4420"(精度:4)
: 100分钟
权重: 10

余额 2:
DestinationIDs: "Dest_UK_All" # 前缀:"44"(精度:2)
: 200分钟
权重: 10

# 余额 1 首先被消耗(精度 4 > 精度 2)
# 更具体的目的地匹配胜出

用例: 城市特定或区域特定的余额优先于国家范围的余额。

规则 2:权重优先(相同精度)

当目的地精度相等时,具有 更高权重 的余额首先被消耗。

余额 1:
ID: "Premium_Data"
: 5GB
权重: 20

余额 2:
ID: "Standard_Data"
: 10GB
权重: 10

# 余额 1 首先被消耗(权重 20 > 权重 10)
# 即使余额 2 的数据更多

用例: 优先余额(奖励数据在常规数据之前消耗)。

规则 3:最旧优先

当权重匹配时,最旧的余额首先被使用。

余额 1:
ID: "Data_Package_A"
: 5GB
到期: 2024-12-25
权重: 10

余额 2:
ID: "Data_Package_B"
: 10GB
到期: 2025-01-15
权重: 10

要控制消耗顺序,请使用不同的权重:

# 正确的方法:使用权重来优先考虑即将到期的余额
余额 1:
ID: "Data_Package_A"
: 5GB
��期: 2024-12-25
权重: 11 # 更高的权重 = 首先消耗

余额 2:
ID: "Data_Package_B"
: 10GB
到期: 2025-01-15
权重: 10 # 较低的权重 = 第二消耗

# 余额 1 首先被消耗(权重 11 > 权重 10)

最佳实践: 如果您希望确保即将到期的余额首先被消耗,请在创建时为其分配更高的权重。

实际示例

示例 1:简单数据附加产品(独立)

场景: 客户可以多次购买“5GB 5天”附加产品,每次创建一个单独的余额。

实现:

- name: 生成 UUID 用于唯一余额
set_fact:
uuid: "{{ 99999999 | random | to_uuid }}"

- name: 添加独立的 5GB 余额
uri:
url: "http://{{ crm_config.ocs.cgrates }}/jsonrpc"
method: POST
body_format: json
body:
{
"method": "ApierV1.AddBalance",
"params": [{
"Tenant": "{{ crm_config.ocs.ocsTenant }}",
"Account": "{{ service_uuid }}",
"BalanceType": "*data",
"Categories": "*any",
"Balance": {
"ID": "Data_5GB_{{ uuid[0:8] }}",
"Value": 5368709120,
"ExpiryTime": "+120h", # 5天
"Weight": 10
}
}]
}

客户体验:

  • 在 12 �� 24 日购买附加产品 → 获得 5GB,2024 年 12 月 29 日到期
  • 在 12 月 25 日购买附加产品 → 获得 5GB,2024 年 12 月 30 日到期
  • 两个余额共存;消耗顺序取决于余额权重(两个权重均为 10,因此顺序不保证)

示例 2:滚存数据包

场景: “每月 50GB 计划”,客户在提前充值时未使用的数据滚存。

实现:

- name: 添加滚存数据余额
uri:
url: "http://{{ crm_config.ocs.cgrates }}/jsonrpc"
method: POST
body_format: json
body:
{
"method": "ApierV1.AddBalance",
"params": [{
"Tenant": "{{ crm_config.ocs.ocsTenant }}",
"Account": "{{ service_uuid }}",
"BalanceType": "*data",
"Balance": {
"ID": "Rollover_Monthly_50GB",
"Value": 53687091200, # 50GB
"ExpiryTime": "+720h", # 30天
"Weight": 10
}
}]
}

操作类型: 使用默认的 *topup 行为(启用滚存)

客户体验:

  • 第 1 天:获得 50GB,30 天后到期
  • 第 20 天:使用 30GB,剩余 20GB
  • 第 20 天:再次充值 → 获得 70GB(20GB + 50GB),到期延长至第 20 天起 +30 天

示例 3:固定每月计划(无滚存���

场景: “无限 100GB 每月”计划,每月重置为确切的 100GB,无滚存。

实现:

- name: 创建每月重置操作
uri:
url: "http://{{ crm_config.ocs.cgrates }}/jsonrpc"
method: POST
body_format: json
body:
{
"method": "ApierV1.SetActions",
"params": [{
"ActionsId": "Action_Monthly_100GB_Reset",
"Overwrite": true,
"Actions": [{
"Identifier": "*topup_reset",
"BalanceType": "*data",
"Units": 107374182400, # 100GB
"ExpiryTime": "*monthly",
"BalanceId": "Monthly_Plan__107374182400",
"Weight": 10
}]
}]
}

- name: 创建每月 ActionPlan
uri:
url: "http://{{ crm_config.ocs.cgrates }}/jsonrpc"
method: POST
body_format: json
body:
{
"method": "ApierV1.SetActionPlan",
"params": [{
"Id": "ActionPlan_Monthly_100GB",
"ActionPlan": [{
"ActionsId": "Action_Monthly_100GB_Reset",
"Time": "*monthly",
"Weight": 10
}],
"Overwrite": true,
"ReloadScheduler": true
}]
}

- name: 将 ActionPlan 分配给账户
uri:
url: "http://{{ crm_config.ocs.cgrates }}/jsonrpc"
method: POST
body_format: json
body:
{
"method": "ApierV2.SetAccount",
"params": [{
"Account": "{{ service_uuid }}",
"ActionPlanIds": ["ActionPlan_Monthly_100GB"],
"ReloadScheduler": true
}]
}

客户体验:

  • 第 1 个月,第 1 天:获得 100GB,使用 95GB,剩余 5GB
  • 第 2 个月:余额重置为 100GB(未使用的 5GB 丢失)
  • 第 2 个月:使用 20GB,剩余 80GB
  • 第 3 个月:余额重置为 100GB(未使用的 80GB 丢失)

示例 4:多层余额与权重优先

场景: 客户拥有“奖励数据”(高优先级)和“常规数据”(低优先级)。奖励数据优先消耗。

实现:

# 添加高权重的奖励数据
- name: 添加奖励数据余额
uri:
url: "http://{{ crm_config.ocs.cgrates }}/jsonrpc"
method: POST
body_format: json
body:
{
"method": "ApierV1.AddBalance",
"params": [{
"Account": "{{ service_uuid }}",
"BalanceType": "*data",
"Balance": {
"ID": "Bonus_Data",
"Value": 5368709120, # 5GB
"ExpiryTime": "+240h", # 10天
"Weight": 20
}
}]
}

# 添加常规数据,权重正常
- name: 添加常规数据余额
uri:
url: "http://{{ crm_config.ocs.cgrates }}/jsonrpc"
method: POST
body_format: json
body:
{
"method": "ApierV1.AddBalance",
"params": [{
"Account": "{{ service_uuid }}",
"BalanceType": "*data",
"Balance": {
"ID": "Regular_Data",
"Value": 53687091200, # 50GB
"ExpiryTime": "+720h", # 30天
"Weight": 10 # 正常优先级
}
}]
}

客户体验:

  • 拥有 5GB 奖励数据(权重 20)+ 50GB 常规数据(权重 10)
  • 首先消耗所有 5GB 奖励数据
  • 然后从 50GB 常规数据池中消耗
  • 常规数据保留更长时间

常见操作标识符

CGRateS 支持多种操作标识符以进行不同操作:

余额操作

*topup - 添加到现有余额(滚存)
*topup_reset - 将余额重置为新值(无滚存)
*debit - 从余额中扣除
*debit_reset - 将余额设置为负值
*reset_account - 删除所有余额

设计附加产品

在 OmniCRM 中设计附加产品时,请考���以下问题:

问题 1:余额应该堆叠吗?

是(独立) → 使用唯一余额 ID(带 UUID)
否(替换) → 使用固定余额 ID 和 *topup_reset
是(滚存) → 使用固定余额 ID 和 *topup

问题 2:未使用的余额会发生什么?

滚存 → 使用 *topup 操作
丢失 → 使用 *topup_reset 操作
独立池 → 使用唯一余额 ID

问题 3:余额应该如何消耗?

最旧优先 → 使用不同的权重(为较旧/即将到期的余额分配更高的权重)
优先级高 → 不同的权重(更高的权重 = 更高的优先级)
特定顺序 → 使用权重:30(优先),20(奖励),10(常规)
随机/无偏好 → 使用相同的权重(消耗顺序不保证)

问题 4:到期策略是什么?

固定持续时间 → 使用相对到期(+720h 为 30 天)
月底 → 在 ActionPlan 中使用 *monthly
永不到期 → 使用 *unlimited 或非常长的持续时间

CGRateS 操作结构在 Playbooks 中

以下是创建操作的完整结构:

- name: 创建 CGRateS 操作
uri:
url: "http://{{ crm_config.ocs.cgrates }}/jsonrpc"
method: POST
body_format: json
body:
{
"method": "ApierV1.SetActions",
"params": [{
"ActionsId": "Action_Name_Here",
"Overwrite": true, # 如果存在则替换
"Actions": [
{
"Identifier": "*topup", # 或 *topup_reset
"BalanceType": "*data", # *data、*voice、*sms、*monetary
"Units": 5368709120, # 添加的金额
"ExpiryTime": "+120h", # +Xh、*unlimited、*monthly
"DestinationIds": "*any", # 通常为 *any
"BalanceId": "Balance_Name", # 唯一或共享
"Weight": 10, # 优先级(更高 = 首先消耗)
"Blocker": false, # 如果为 true,则防止负余额
"Disabled": false, # 如果为 true,则余额被禁用
"SharedGroups": "" # 共享余额组(可选)
},
{
"Identifier": "*cdrlog", # 将此操作记录为 CDR
"BalanceType": "*generic",
"ExtraParameters": "{\"Category\":\"^activation\",\"Destination\":\"Addon Name\"}"
}
]
}]
}

字段描述

ActionsId - 此操作集的唯一标识符

Identifier - 操作类型(*topup*topup_reset*cdrlog 等)

BalanceType - 余额类型:

  • *data - 数据余额(字节)
  • *voice - 语音余额(秒)
  • *sms - 短信余额(计数)
  • *monetary - 货币余额(货币单位)
  • *generic - 通用余额

Units - 添加/扣除的金额(基本单位:数据为字节,语音为秒)

ExpiryTime - 余额到期时间:

  • +Xh - 相对(例如,+720h = 30 天)
  • *unlimited - 永不到期
  • *monthly - 月底
  • 2024-12-31T23:59:59Z - 绝对时间戳

BalanceId - 此余额的标识符(共享 ID = 交互,唯一 ID = 独立)

Weight - 优先级(更高的数字 = 更高的优先级,首先消耗)

Blocker - 如果为 true,则防止账户变为负数

Disabled - 如果为 true,则余额存在但无法使用

通过 Python 定义操作(初始设置)

操作通常在初始系统配置期间使用 Python 脚本通过 cgrateshttpapi 库定义。这些示例展示了如何使用 OCS_Obj.SendData() 定义操作。

先决条件

import cgrateshttpapi
import time

OCS_Obj = cgrateshttpapi.CGRateS("ocs.example.com", "2080")
tenant = "your_tenant_name"
tpid = str(tenant) + "_" + str(int(time.time()))

定义目的地

在创建操作之前,您必须定义目的地,以指定余额可以在哪里使用。

目的地 有两种类型:

  • 地理目的地 - 语音/SMS 的号码前缀(例如,Dest_International_UK
  • PLMN 目的地 - 数据的网络代码(例如,Dest_PLMN_OnNetDest_PLMN_US_Verizon

关键规则:

  • 语音/SMS 余额 → 使用地理目的地(拨打的号码 TO)
  • 数据余额 → 使用 PLMN 目的地(客户连接的网络 FROM)

有关完整的目的地配置,包括:

  • 地理目的地(国内、国际、免费电话、高级)
  • PLMN 目的地(本网、漫游网络、区域)
  • PLMN 格式规则和最佳实践
  • 故障排除目的地问题

请参见:CGRateS 目的地配置

单位计算

理解单位转换对于正确定义余额至关重要:

# 数据余额(以字节为单位)
1_GB = 1 * 1024 * 1024 * 1024 # 1073741824 字节
100_GB = 100 * 1024 * 1024 * 1024

# 语音余额(以纳秒为单位)
1_minute = 60 * 1000000000 # 60 亿纳秒
3000_minutes = 3000 * 60 * 1000000000

# 短信余额(以计数为单位)
3000_sms = 3000

示例 1:多余额每月计划(Python)

一个综合的每月计划,包含数据、语音、短信和漫游余额,每月重置。

Action_AU_Premium_Plan_1 = {
"id": "0",
"method": "ApierV1.SetActions",
"params": [{
"ActionsId": "Action_au-premium-plan-1",
"Overwrite": True,
"Tenant": str(tenant),
"Actions": [
# 首先重置账户以清除旧余额
{
"Identifier": "*reset_account",
"Weight": 700
},
# 添加 100GB 数据余额
# 重要:数据余额使用 PLMN 目的地(客户连接的网络)
# 而不是地理目的地。使用您的本网 PLMN。
{
"Identifier": "*topup_reset",
"BalanceId": "AU_Data_Domestic__" + str(100 * 1024 * 1024 * 1024),
"BalanceType": "*data",
"DestinationIDs": "Dest_PLMN_OnNet", # 您的本网 PLMN(mcc505.mnc057)
"Units": 100 * 1024 * 1024 * 1024,
"ExpiryTime": "*month",
"BalanceWeight": 1200,
"Weight": 90
},
# 添加 3000 分钟语音余额
{
"Identifier": "*topup_reset",
"BalanceId": "AU_Voice_Domestic__" + str(3000 * 60 * 1000000000),
"BalanceType": "*voice",
"DestinationIDs": "Dest_AU_Mobile;Dest_AU_Fixed;Dest_AU_TollFree;",
"Units": 3000 * 60 * 1000000000,
"ExpiryTime": "*month",
"BalanceWeight": 1200,
"Weight": 89
},
# 添加 3000 短信余额
{
"Identifier": "*topup_reset",
"BalanceId": "AU_SMS_Domestic__" + str(3000),
"BalanceType": "*sms",
"DestinationIDs": "Dest_AU_Mobile;",
"Units": 3000,
"ExpiryTime": "*month",
"BalanceWeight": 1200,
"Weight": 88
},
# 添加 6GB 漫游数据
{
"Identifier": "*topup_reset",
"BalanceId": "AU_Roaming_Data__" + str(6 * 1024 * 1024 * 1024),
"BalanceType": "*data",
"DestinationIDs": "Dest_Roaming_All",
"Units": 6 * 1024 * 1024 * 1024,
"ExpiryTime": "*month",
"BalanceWeight": 1100,
"Weight": 87
},
# 将此操作记录为 CDR
{
"Identifier": "*cdrlog",
"BalanceType": "*generic",
"ExtraParameters": "{\"Category\":\"^activation\",\"Destination\":\"AU Premium Plan 1\"}"
}
]
}]
}

# 将操作定义发送到 CGRateS
result = OCS_Obj.SendData(Action_AU_Premium_Plan_1)
assert result['error'] is None or result['error'] == "EXISTS"
print("创建操作:Action_au-premium-plan-1")

关键点:

  • 使用 *reset_account 首先清除旧余额
  • 使用 *topup_reset 进行固定的每月津贴(无滚存)
  • BalanceWeight 决定消耗顺序(国内 1200 > 漫游 1100)
  • Weight 决定操作集内的执行顺序
  • 包含 *cdrlog 用于跟踪激活

示例 2:简单数据附加产品(Python)

一个简单的 20GB 数据附加产品,滚存被禁用。

Action_AU_Data_Addon_20GB = {
"id": "0",
"method": "ApierV1.SetActions",
"params": [{
"ActionsId": "Action_au-data-addon-20gb",
"Overwrite": True,
"Tenant": str(tenant),
"Actions": [
# 首先重置账户
{
"Identifier": "*reset_account",
"Weight": 700
},
# 添加 20GB 数据
# 数据余额使用 PLMN 目的地(客户连接的网络)
{
"Identifier": "*topup_reset",
"BalanceId": "AU_Data_Domestic__" + str(20 * 1024 * 1024 * 1024),
"BalanceType": "*data",
"DestinationIDs": "Dest_PLMN_OnNet", # 您的本网 PLMN(mcc505.mnc057)
"Units": 20 * 1024 * 1024 * 1024,
"ExpiryTime": "*month",
"BalanceWeight": 1200,
"Weight": 90
}
]
}]
}

result = OCS_Obj.SendData(Action_AU_Data_Addon_20GB)
assert result['error'] is None or result['error'] == "EXISTS"
print("创建操作:Action_au-data-addon-20gb")

示例 3:国际语音附加产品(Python)

一个用于国际通话分钟的附加产品。

Action_AU_International_Voice_100min = {
"id": "0",
"method": "ApierV1.SetActions",
"params": [{
"ActionsId": "Action_au-international-voice-100min",
"Overwrite": True,
"Tenant": str(tenant),
"Actions": [
{
"Identifier": "*reset_account",
"Weight": 700
},
# 添加 100 分钟国际通话
{
"Identifier": "*topup_reset",
"BalanceId": "AU_Voice_International__" + str(100 * 60 * 1000000000),
"BalanceType": "*voice",
"DestinationIDs": "Dest_International_All",
"Units": 100 * 60 * 1000000000,
"ExpiryTime": "*month",
"BalanceWeight": 1000,
"Weight": 90
}
]
}]
}

result = OCS_Obj.SendData(Action_AU_International_Voice_100min)
assert result['error'] is None or result['error'] == "EXISTS"
print("创建操作:Action_au-international-voice-100min")

注意: 语音/SMS 使用地理目的地(拨打的号码),而数据使用 PLMN 目的地(客户连接的网络)。请参见 定义产品 以获取目的地配置。

Python 操作字段参考

操作定义字段:

  • ActionsId(必需) - 此操作集的唯一标识符(必须与 CRM 中的 product_slug 约定匹配)
  • Overwrite - 如果为 True,则替换具有相同 ID 的现有操作
  • Tenant - CGRateS 租户名称
  • Actions - 要执行的单个操作的数组

单个操作字段:

  • Identifier - 操作类型(*topup*topup_reset*cdrlog 等)
  • BalanceId - 此余额的唯一标识符(必须在滚存时匹配)
  • BalanceType - 余额类型(*data*voice*sms*monetary
  • DestinationIDs - 控制余额可以使用的地方:
    • 对于 语音/SMS:使用地理目的地(例如,"Dest_AU_Mobile""Dest_International_UK"
    • 对于 数据:使用 PLMN 目的地(例如,"Dest_PLMN_OnNet""Dest_PLMN_US_Verizon"
  • Units - 添加的金额(字节用于数据,秒用于语音)
  • ExpiryTime - 余额到期时间(*month+720h2024-12-31 等)
  • BalanceWeight - 消耗优先级(更高 = 首先消耗)
  • Weight - 操作集内的执行顺序(更高 = 首先执行)
  • Blocker(可选) - 如果为 True,则防止账户变为负数
  • Disabled(可选) - 如果为 True,则此余额被忽略/跳过

执行操作

一旦创建了操作,就可以在账户上执行它:

- name: 在账户上执行操作
uri:
url: "http://{{ crm_config.ocs.cgrates }}/jsonrpc"
method: POST
body_format: json
body:
{
"method": "APIerSv1.ExecuteAction",
"params": [{
"Tenant": "{{ crm_config.ocs.ocsTenant }}",
"Account": "{{ service_uuid }}",
"ActionsId": "Action_Name_Here"
}]
}

这将在指定账户上执行操作中定义的所有操作。

余额管理方法

在 CGRateS 中,主要有三种管理余额的方法,每种方法在客户体验、账单可预测性和操作复杂性方面都有不同的权衡。

比较:单元制 vs 货币制 vs 混合

下表总结了三种余额方法之间的权衡:

特征单元制货币制(PAYG)混合
客户可预测性✅ 固定每月费用❌ 可变费用⚠️ 大部分可预测
目的地灵活性❌ 限于包含的目的地✅ 在任何地方拨打/使用✅ 包含 + 任何地方
超支处理❌ 硬截止✅ 自动使用✅ 自动溢出
账单冲击风险✅ 低(硬上限)❌ 高(无限计费)⚠️ 中等(上限溢出)
配置复杂性⚠️ 中等(许多余额)✅ 简单(一个余额)❌ 复杂(两者)
收入优化⚠️ 较低的 ARPU✅ 高 ARPU 来自重度用户✅ 平衡的 ARPU
客户满意度✅ 高(没有惊喜)❌ 低(账单冲击)✅ 高(两者的最佳)
最佳适用对象可预测的用户偶尔用户大多数客户

方法 1:单元制余额

概念: 提供特定数量用于特定目的地。每个余额都有固定的金额(分钟、GB、短信计数),与特定目的地相关联。当耗尽时,使用被阻止,除非有货币后备。

示例:国内计划与多个余额

Action_Domestic_Plan = {
"id": "0",
"method": "ApierV1.SetActions",
"params": [{
"ActionsId": "Action_domestic-plan",
"Overwrite": True,
"Tenant": tenant,
"Actions": [
{
"Identifier": "*reset_account",
"Weight": 700
},
# 500分钟国内通话
{
"Identifier": "*topup_reset",
"BalanceId": f"Domestic_Voice__{500 * 60 * 1000000000}",
"BalanceType": "*voice",
"DestinationIDs": "Dest_Domestic_All", # 仅限国内
"Units": 500 * 60 * 1000000000, # 500分钟(以纳秒为单位)
"ExpiryTime": "*month",
"BalanceWeight": 1200,
"Weight": 90
},
# 1000条国内短信
{
"Identifier": "*topup_reset",
"BalanceId": "Domestic_SMS__1000",
"BalanceType": "*sms",
"DestinationIDs": "Dest_Domestic_All",
"Units": 1000,
"ExpiryTime": "*month",
"BalanceWeight": 1200,
"Weight": 89
},
# 10GB 国内数据
{
"Identifier": "*topup_reset",
"BalanceId": f"Domestic_Data__{10 * 1024 * 1024 * 1024}",
"BalanceType": "*data",
"DestinationIDs": "Dest_PLMN_OnNet", # 您的本网 PLMN
"Units": 10 * 1024 * 1024 * 1024,
"ExpiryTime": "*month",
"BalanceWeight": 1200,
"Weight": 88
},
{
"Identifier": "*cdrlog",
"BalanceType": "*generic",
"ExtraParameters": "{\"Category\":\"^activation\",\"Destination\":\"Domestic Plan\"}",
"Weight": 80
}
]
}]
}

result = OCS_Obj.SendData(Action_Domestic_Plan)
assert result['error'] is None or result['error'] == "EXISTS"

工作原理:

  • 国内通话(1-555-1234) → 使用 500 分钟余额
  • 国际通话(44-20-xxx) → 没有可用余额,被阻止或使用货币余额(如果有)
  • 国内短信 → 使用 1000 短信余额
  • 家庭数据使用 → 使用 10GB 余额
  • 漫游数据 → 没有可用余额(需要单独的漫游数据余额)

优点:

  • 客户可预测的费用
  • 没有账单冲击
  • 清晰的限制

缺点:

  • 灵活性较差 - 不能在包含的目的地之外使用服务
  • 需要多个余额以适应不同的用例
  • 客户可能会感到受限

方法 2:货币制(PAYG)

概念: 提供货币信用,按目的地特定费率收费。单一货币余额用于所有使用类型。CGRateS 查找每个目的地的费率并从信用中扣除费用。

注意: PAYG 需要为每个目的地定义费率配置文件,以设置每单位的美元金额。请参见 PAYG/货币余额的费率配置文件 以获取完整配置。

示例:$50 PAYG 信用

Action_PAYG_Credit = {
"id": "0",
"method": "ApierV1.SetActions",
"params": [{
"ActionsId": "Action_payg-50-credit",
"Overwrite": True,
"Tenant": tenant,
"Actions": [
# $50 货币余额
{
"Identifier": "*topup",
"BalanceId": "PAYG_Monetary_Balance",
"BalanceType": "*monetary",
"DestinationIDs": "*any", # 适用于任何目的地
"Units": 5000, # $50.00(以美分为单位)
"ExpiryTime": "+2160h", # 90天
"BalanceWeight": 1000, # 低于单元制余额
"Weight": 90
},
{
"Identifier": "*cdrlog",
"BalanceType": "*generic",
"ExtraParameters": "{\"Category\":\"^activation\",\"Destination\":\"$50 PAYG Credit\"}",
"Weight": 80
}
]
}]
}

result = OCS_Obj.SendData(Action_PAYG_Credit)

PAYG 的工作原理:

场景 1:国内通话,10分钟

  • 费率:$0.10/分钟
  • 收费:10 × $0.10 = $1.00
  • 剩余:$49.00

场景 2:拨打英国电话,5分钟

  • 费率:$0.25/分钟 + $0.05 连接费
  • 收费:(5 × $0.25) + $0.05 = $1.30
  • 剩余:$47.70

场景 3:在 Verizon 漫游,使用 100MB 数据

  • 费率