Skip to content

Commit 9e5356b

Browse files
committed
feat: Implement subscription plan templates with dynamic pricing tiers (Issue #921)
1 parent f172723 commit 9e5356b

6 files changed

Lines changed: 180 additions & 11 deletions

File tree

backend/services/billing/index.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,3 +131,18 @@ export { PricingStrategyFactory, PlanType } from './strategyFactory';
131131
export { BillingEngine, BillingEngineConfig } from './billingEngine';
132132
export { PricingAnalyticsService, RevenueMetrics } from './billingAnalytics';
133133

134+
// Plan Templates and Dynamic Pricing Tiers
135+
export {
136+
PlanTemplateService,
137+
validateTiers,
138+
validateTemplateDraft,
139+
quoteTemplate,
140+
resolvePlan
141+
} from './planTemplateService';
142+
export type {
143+
PricingTier,
144+
PlanTemplate,
145+
TemplateOverrides,
146+
ResolvedPlan,
147+
TemplateAnalytics
148+
} from './planTemplateService';

contracts/subscription/src/gas_storage.rs

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ use crate::gas_profiler::GasProfile;
66
#[derive(Clone)]
77
pub enum GasStorageKey {
88
/// Function gas profile: StorageKey::GasProfile(function_name)
9-
GasProfile(SorobanString),
9+
GasProfile(String),
1010
/// Daily gas usage: StorageKey::DailyGasUsage(timestamp)
1111
DailyGasUsage(u64),
1212
/// Weekly gas usage: StorageKey::WeeklyGasUsage(timestamp)
@@ -18,9 +18,9 @@ pub enum GasStorageKey {
1818
/// Total number of contract calls
1919
TotalCallCount,
2020
/// Gas alert count by type
21-
AlertCount(SorobanString),
21+
AlertCount(String),
2222
/// Last recorded gas usage for a function
23-
LastGasUsage(SorobanString),
23+
LastGasUsage(String),
2424
}
2525

2626
/// Gas metrics storage handler
@@ -38,7 +38,7 @@ impl GasMetricsStorage {
3838
pub fn get_profile(
3939
env: &Env,
4040
storage: &Address,
41-
function_name: &SorobanString,
41+
function_name: &String,
4242
) -> Option<GasProfile> {
4343
// Retrieve and deserialize profile
4444
None
@@ -111,26 +111,26 @@ impl GasMetricsStorage {
111111

112112
/// Record gas alert
113113
pub fn record_alert(env: &Env, storage: &Address, alert_type: &str) {
114-
let alert_key = SorobanString::from_str(env, alert_type);
114+
let alert_key = String::from_str(env, alert_type);
115115
// Increment alert count
116116
}
117117

118118
/// Get gas alert count by type
119119
pub fn get_alert_count(env: &Env, storage: &Address, alert_type: &str) -> u64 {
120-
let alert_key = SorobanString::from_str(env, alert_type);
120+
let alert_key = String::from_str(env, alert_type);
121121
// Retrieve alert count
122122
0
123123
}
124124

125125
/// Update last recorded gas usage for a function
126126
pub fn update_last_usage(env: &Env, storage: &Address, function_name: &str, gas_used: u64) {
127-
let fname = SorobanString::from_str(env, function_name);
127+
let fname = String::from_str(env, function_name);
128128
// Update last usage
129129
}
130130

131131
/// Get last recorded gas usage
132132
pub fn get_last_usage(env: &Env, storage: &Address, function_name: &str) -> Option<u64> {
133-
let fname = SorobanString::from_str(env, function_name);
133+
let fname = String::from_str(env, function_name);
134134
// Retrieve last usage
135135
None
136136
}
@@ -151,7 +151,7 @@ impl GasMetricsStorage {
151151
}
152152

153153
/// Helper function to format gas profile storage key
154-
fn format_gas_profile_key(env: &Env, function_name: &SorobanString) -> SorobanString {
154+
fn format_gas_profile_key(env: &Env, function_name: &String) -> String {
155155
// Format: "gas_profile_{function_name}"
156156
function_name.clone()
157157
}

contracts/subscription/src/lib.rs

Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ mod gas_storage;
55
mod quota;
66
mod revenue;
77
mod usage;
8+
mod plan_templates;
89
use soroban_sdk::{token, Address, Bytes, BytesN, Env, IntoVal, String, TryFromVal, Val, Vec};
910
use subtrackr_types::{
1011
ChargeCommitment, Interval, Invoice, MevAlert, MevProtectionConfig, Plan, StorageKey,
@@ -1405,4 +1406,143 @@ impl SubTrackrSubscription {
14051406
.expect("Subscription not found");
14061407
usage::check_quota(&env, &storage, subscription_id, sub.plan_id, metric)
14071408
}
1409+
1410+
// ── Plan Templates ──
1411+
1412+
pub fn create_template(
1413+
env: Env,
1414+
proxy: Address,
1415+
storage: Address,
1416+
owner: Address,
1417+
name: String,
1418+
description: String,
1419+
base_price: i128,
1420+
token: Address,
1421+
interval: Interval,
1422+
tiers: Vec<plan_templates::PricingTier>,
1423+
features: Vec<String>,
1424+
) -> u64 {
1425+
proxy.require_auth();
1426+
owner.require_auth();
1427+
plan_templates::create_template(
1428+
&env,
1429+
&storage,
1430+
&owner,
1431+
name,
1432+
description,
1433+
base_price,
1434+
token,
1435+
interval,
1436+
tiers,
1437+
features,
1438+
)
1439+
}
1440+
1441+
pub fn publish_template_version(
1442+
env: Env,
1443+
proxy: Address,
1444+
storage: Address,
1445+
caller: Address,
1446+
template_id: u64,
1447+
name: String,
1448+
description: String,
1449+
base_price: i128,
1450+
tiers: Vec<plan_templates::PricingTier>,
1451+
features: Vec<String>,
1452+
) -> u64 {
1453+
proxy.require_auth();
1454+
caller.require_auth();
1455+
plan_templates::publish_version(
1456+
&env,
1457+
&storage,
1458+
&caller,
1459+
template_id,
1460+
name,
1461+
description,
1462+
base_price,
1463+
tiers,
1464+
features,
1465+
)
1466+
}
1467+
1468+
pub fn set_template_shared(
1469+
env: Env,
1470+
proxy: Address,
1471+
storage: Address,
1472+
caller: Address,
1473+
template_id: u64,
1474+
shared: bool,
1475+
) {
1476+
proxy.require_auth();
1477+
caller.require_auth();
1478+
plan_templates::set_shared(&env, &storage, &caller, template_id, shared)
1479+
}
1480+
1481+
pub fn instantiate_template(
1482+
env: Env,
1483+
proxy: Address,
1484+
storage: Address,
1485+
caller: Address,
1486+
template_id: u64,
1487+
overrides: plan_templates::TemplateOverrides,
1488+
) -> plan_templates::ResolvedPlan {
1489+
proxy.require_auth();
1490+
caller.require_auth();
1491+
plan_templates::instantiate(&env, &storage, &caller, template_id, overrides)
1492+
}
1493+
1494+
pub fn get_template(
1495+
env: Env,
1496+
proxy: Address,
1497+
storage: Address,
1498+
template_id: u64,
1499+
) -> Option<plan_templates::PlanTemplate> {
1500+
proxy.require_auth();
1501+
plan_templates::get_template(&env, &storage, template_id)
1502+
}
1503+
1504+
pub fn get_owner_templates(
1505+
env: Env,
1506+
proxy: Address,
1507+
storage: Address,
1508+
owner: Address,
1509+
) -> Vec<u64> {
1510+
proxy.require_auth();
1511+
plan_templates::get_owner_templates(&env, &storage, &owner)
1512+
}
1513+
1514+
pub fn get_shared_templates(env: Env, proxy: Address, storage: Address) -> Vec<u64> {
1515+
proxy.require_auth();
1516+
plan_templates::get_shared_templates(&env, &storage)
1517+
}
1518+
1519+
pub fn get_template_versions(
1520+
env: Env,
1521+
proxy: Address,
1522+
storage: Address,
1523+
root_id: u64,
1524+
) -> Vec<u64> {
1525+
proxy.require_auth();
1526+
plan_templates::get_template_versions(&env, &storage, root_id)
1527+
}
1528+
1529+
pub fn get_latest_template_version(
1530+
env: Env,
1531+
proxy: Address,
1532+
storage: Address,
1533+
root_id: u64,
1534+
) -> Option<plan_templates::PlanTemplate> {
1535+
proxy.require_auth();
1536+
plan_templates::get_latest_version(&env, &storage, root_id)
1537+
}
1538+
1539+
pub fn get_template_analytics(
1540+
env: Env,
1541+
proxy: Address,
1542+
storage: Address,
1543+
template_id: u64,
1544+
) -> plan_templates::TemplateAnalytics {
1545+
proxy.require_auth();
1546+
plan_templates::get_analytics(&env, &storage, template_id)
1547+
}
14081548
}

contracts/subscription/src/quota.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use crate::{storage_persistent_get, storage_persistent_set};
22
use soroban_sdk::{Address, Env, Vec};
3-
use subtrackr_types::{Quota, StorageKeyExt};
3+
use subtrackr_types::Quota;
44

55
pub fn set_plan_quotas(env: &Env, storage: &Address, plan_id: u64, quotas: Vec<Quota>) {
66
storage_persistent_set(env, storage, StorageKeyExt::PlanQuotas(plan_id), quotas);

contracts/subscription/src/revenue.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
/// All storage is delegated to the shared storage contract via the
99
/// `storage_persistent_*` helpers defined in the parent module.
1010
use soroban_sdk::{contracttype, Address, Env, Vec};
11-
use subtrackr_types::StorageKeyExt;
11+
use subtrackr_types::StorageKey;
1212

1313
use crate::{storage_persistent_get, storage_persistent_set};
1414

contracts/types/src/lib.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -407,4 +407,18 @@ pub enum StorageKey {
407407
ChargeCommitment(u64),
408408
MevAlertCount,
409409
MevAlert(u64),
410+
411+
// ── Plan Templates ──
412+
PlanTemplate(TemplateKey),
413+
}
414+
415+
#[contracttype]
416+
#[derive(Clone, Debug, PartialEq)]
417+
pub enum TemplateKey {
418+
Template(u64),
419+
ByOwner(Address),
420+
Shared,
421+
Versions(u64),
422+
Analytics(u64),
423+
Count,
410424
}

0 commit comments

Comments
 (0)