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 中不存在,则提供失败。
在哪里定义操作
操作应在您的产品配置脚本中定义,通常在:
- 初始设置期间 - 在首次配置系统时
- 创建新产品时 - 在创建产品之前定义操作
- 通过配置脚本 - 配置 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 处理逻辑:
-
解析余额 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 -
生成可读的描述性名称:
# 用空格替换下划线
id_hr = descriptive_name.replace("_", " ") # "AU Data Domestic" -
将原始大小转换为人类单位:
# 对于数据余额(字节)
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" -
计算使用百分比:
if original_size and original_size > 0:
percent_used = ((original_size - current_value) / original_size) * 100 -
格式化剩余显示:
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>
为什么这很重要:
-
原始大小跟踪 - 即使余额部分消耗,UI 也可以显示 "50 GB of 100 GB",而不仅仅是 "50 GB 剩余"
-
进度可视化 - 百分比计算使准确的进度条成为可能
-
一致的命名 - 从余额 ID 中提取的描述性名称确保后端和前端之间的一致性
-
滚存显示 - 当使用
*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 保持不变:
最佳实践 - 创建余额 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 时:
- 当阻止器有值时 → 允许使用,直到阻止器金额
- 当阻止器达到零时 → 所有使用停止,即使存在其他余额
- 返回错误 →
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
}
]
}]
}
流程:
- 客户使用 10GB 包含 → 免费(来自 Included_Data_10GB)
- 客户使用额外的 5GB → 从 Overage_Cap 收费(按 PAYG 费率)
- 当 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) → 被阻止
阻止器与禁用余额的区别
不要将 Blocker 与 Disabled 混淆:
| 特征 | 阻止器 | 禁用 |
|---|---|---|
| 目的 | 当余额耗尽时停止使用 | 暂时暂停余额 |
| 当值 > 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
}
]
}]
}
客户旅程:
- 0-500分钟: 使用 Domestic_Voice_500min(免费)
- 500-700分钟: 使用 Overage_Allowance,每分钟 $0.10 = $20(200分钟)
- 700-1200分钟: 使用 Hard_Spending_Cap,每分钟 $0.10 = $50(500分钟)
- 在1200分钟时: Hard_Spending_Cap 耗尽 → 所有使用被阻止
客户获得总共 1200 分钟,超支最大为 $50。
最佳实践与阻止器
-
为阻止器使用高权重
"BalanceWeight": 9999 # 确保阻止器首先被检查 -
零值阻止器以实现立即阻止
"Units": 0, # 立即阻止
"Blocker": True -
在阻止器耗尽之前通知客户
- 使用 ActionTriggers 在 80%、90%、100% 阻止器使用时发送通知
- 在阻止发生之前给客户增加上限的选项
-
在取消暂停时移除阻止器
# 使用 *remove_balance 删除阻��器
{
"Identifier": "*remove_balance",
"BalanceId": "Suspension_Blocker"
} -
测试阻止器行为
- 验证阻止器返回
INSUFFICIENT_CREDIT_BALANCE_BLOCKER错误 - 确认 CDR 显示成本 = -1.0 当被阻止时
- 测试在阻止器耗尽后其他余额不被使用
- 验证阻止器返回
故障排除阻止器
问题:尽管阻止器为零,但使用未被阻止
可能原因:
- 阻止器权重太低(其他余额首先检查)
- DestinationIDs 不匹配使用目的地
- 阻止器字段未设置为
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_OnNet、Dest_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")
- 对于 语音/SMS:使用地理目的地(例如,
- Units - 添加的金额(字节用于数据,秒用于语音)
- ExpiryTime - 余额到期时间(
*month、+720h、2024-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 数据
- 费率