Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
107 changes: 107 additions & 0 deletions src/chainlib/ChainLib.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,14 @@ pub mod ChainLib {
Art,
}

#[derive(Copy, Drop, Serde, starknet::Store, PartialEq, Debug)]
pub enum SubscriptionStatus {
Active,
#[default]
Inactive,
Cancelled,
}

#[derive(Copy, Drop, Serde, starknet::Store, Debug)]
pub struct ContentMetadata {
pub content_id: felt252,
Expand All @@ -79,6 +87,7 @@ pub mod ChainLib {
pub is_active: bool,
pub last_payment_date: u64,
pub subscription_type: PlanType,
pub status: SubscriptionStatus,
}

#[derive(Drop, Serde, starknet::Store, Clone, PartialEq)]
Expand Down Expand Up @@ -241,6 +250,8 @@ pub mod ChainLib {
DelegationExpired: DelegationExpired,
ContentPurchased: ContentPurchased,
PurchaseStatusUpdated: PurchaseStatusUpdated,
SubscriptionCancelled: SubscriptionCancelled,
SubscriptionRenewed: SubscriptionRenewed,
}

#[derive(Drop, starknet::Event)]
Expand All @@ -260,6 +271,18 @@ pub mod ChainLib {
pub amount: u256,
}

#[derive(Drop, starknet::Event)]
struct SubscriptionRenewed {
user: ContractAddress,
subscription_id: u256,
new_end_time: u64,
}

#[derive(Drop, starknet::Event)]
struct SubscriptionCancelled {
user: ContractAddress,
subscription_id: u256,
}
#[derive(Drop, starknet::Event)]
pub struct AccessVerified {
pub user_id: u256,
Expand Down Expand Up @@ -756,6 +779,7 @@ pub mod ChainLib {
is_active: true,
last_payment_date: current_time,
subscription_type: subscription_plan.subscription_type,
status: subscription_plan.status,
};

// Store the subscription
Expand Down Expand Up @@ -1429,6 +1453,7 @@ pub mod ChainLib {
is_active: true,
last_payment_date: current_time,
subscription_type: subscription_type,
status: SubscriptionStatus::Active,
};

self.subscriptions.write(user_id, new_subscription.clone());
Expand Down Expand Up @@ -1859,5 +1884,87 @@ pub mod ChainLib {

true
}

fn cancel_subscription(ref self: ContractState, user_id: u256) -> bool {
let caller = get_caller_address();

// Verify the user exists
let user = self.users.read(user_id);
assert(user.id == user_id, 'User does not exist');

let subscription_plan: Subscription = self.subscriptions.read(user_id);

// update user_id subscription to cancelled
let update_subscription = Subscription {
id: subscription_plan.id,
subscriber: subscription_plan.subscriber,
plan_id: subscription_plan.plan_id,
amount: subscription_plan.amount,
start_date: subscription_plan.start_date,
end_date: subscription_plan.end_date,
is_active: false,
last_payment_date: subscription_plan.last_payment_date,
subscription_type: subscription_plan.subscription_type,
status: SubscriptionStatus::Cancelled,
};

// Store the subscription
self.subscriptions.write(user_id, update_subscription.clone());

self.subscription_record.entry(user_id).append().write(update_subscription);

let current_count = self.subscription_count.read(user_id);

self.emit(SubscriptionCancelled { user: caller, subscription_id: user_id });

true
}

fn renew_subscription(ref self: ContractState, user_id: u256) -> bool {
let caller = get_caller_address();

// Verify the user exists
let user = self.users.read(user_id);
assert(user.id == user_id, 'User does not exist');

// let subscription_id = self.subscription_id.read();
let subscription_plan: Subscription = self.subscriptions.read(user_id);

let current_time = get_block_timestamp();

// Default subscription period is 30 days (in seconds)
let subscription_period: u64 = 30 * 24 * 60 * 60;
let end_date = current_time + subscription_period;

// update user_id subscription to renew the previous subscription
let update_subscription = Subscription {
id: subscription_plan.id,
subscriber: subscription_plan.subscriber,
plan_id: subscription_plan.plan_id,
amount: subscription_plan.amount,
start_date: subscription_plan.start_date,
end_date: end_date,
is_active: true,
last_payment_date: subscription_plan.last_payment_date,
subscription_type: subscription_plan.subscription_type,
status: SubscriptionStatus::Active,
};

// Store the subscription
self.subscriptions.write(user_id, update_subscription.clone());

self.subscription_record.entry(user_id).append().write(update_subscription);

let current_count = self.subscription_count.read(user_id);

self
.emit(
SubscriptionRenewed {
user: caller, subscription_id: user_id, new_end_time: end_date,
},
);

true
}
}
}
2 changes: 2 additions & 0 deletions src/interfaces/IChainLib.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -211,4 +211,6 @@ pub trait IChainLib<TContractState> {
fn get_content_purchases(ref self: TContractState, content_id: felt252) -> Array<Purchase>;

fn get_user_subscription_record(ref self: TContractState, user_id: u256) -> Array<Subscription>;
fn cancel_subscription(ref self: TContractState, user_id: u256) -> bool;
fn renew_subscription(ref self: TContractState, user_id: u256) -> bool;
}
73 changes: 72 additions & 1 deletion tests/test_ChainLib.cairo
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// Import the contract modules
use chain_lib::base::types::{PurchaseStatus, Rank, Role, Status};
use chain_lib::chainlib::ChainLib;
use chain_lib::chainlib::ChainLib::ChainLib::{Category, ContentType, PlanType};
use chain_lib::chainlib::ChainLib::ChainLib::{Category, ContentType, PlanType, SubscriptionStatus};
use chain_lib::interfaces::IChainLib::{IChainLib, IChainLibDispatcher, IChainLibDispatcherTrait};
use snforge_std::{
CheatSpan, ContractClassTrait, DeclareResultTrait, cheat_caller_address, declare,
Expand Down Expand Up @@ -331,6 +331,7 @@ fn test_create_subscription() {
let subscription = dispatcher.get_user_subscription(account_id);
assert(subscription.id == 1, 'Subscription ID should be 1');
assert(subscription.subscription_type == PlanType::YEARLY, 'Plan type should be YEARLY');
assert(subscription.status == SubscriptionStatus::Active, 'Plan type should be YEARLY');
let subscription_record = dispatcher.get_user_subscription_record(account_id);
assert(subscription_record.len() == 1, 'record should have length 1');
}
Expand Down Expand Up @@ -748,3 +749,73 @@ fn test_update_nonexistent_purchase() {
let nonexistent_purchase_id = 42_u256;
let _ = dispatcher.update_purchase_status(nonexistent_purchase_id, PurchaseStatus::Completed);
}

#[test]
fn test_cancel_subscription() {
let (contract_address, _) = setup();
let dispatcher = IChainLibDispatcher { contract_address };

// Test input values
let username: felt252 = 'John';
let role: Role = Role::READER;
let rank: Rank = Rank::BEGINNER;
let metadata: felt252 = 'john is a boy';

// Call register
let account_id = dispatcher.register_user(username, role.clone(), rank.clone(), metadata);

dispatcher.create_subscription(account_id, 500, 1);
let subscription = dispatcher.get_user_subscription(account_id);
assert(subscription.id == 1, 'Subscription ID should be 1');
assert(subscription.subscription_type == PlanType::YEARLY, 'Plan type should be YEARLY');
assert(subscription.status == SubscriptionStatus::Active, 'Plan type should be YEARLY');
let subscription_record = dispatcher.get_user_subscription_record(account_id);
assert(subscription_record.len() == 1, 'record should have length 1');

dispatcher.cancel_subscription(account_id);
let subscription = dispatcher.get_user_subscription(account_id);
assert(subscription.id == 1, 'Subscription ID should be 1');
assert(subscription.subscription_type == PlanType::YEARLY, 'Plan type should be YEARLY');
assert(subscription.status == SubscriptionStatus::Cancelled, 'Plan status should be cancelled');
let subscription_record = dispatcher.get_user_subscription_record(account_id);
assert(subscription_record.len() == 1, 'record should have length 1');
}

#[test]
fn test_renew_subscription() {
let (contract_address, _) = setup();
let dispatcher = IChainLibDispatcher { contract_address };

// Test input values
let username: felt252 = 'John';
let role: Role = Role::READER;
let rank: Rank = Rank::BEGINNER;
let metadata: felt252 = 'john is a boy';

// Call register
let account_id = dispatcher.register_user(username, role.clone(), rank.clone(), metadata);

dispatcher.create_subscription(account_id, 500, 1);
let subscription = dispatcher.get_user_subscription(account_id);
assert(subscription.id == 1, 'Subscription ID should be 1');
assert(subscription.subscription_type == PlanType::YEARLY, 'Plan type should be YEARLY');
assert(subscription.status == SubscriptionStatus::Active, 'Plan type should be YEARLY');
let subscription_record = dispatcher.get_user_subscription_record(account_id);
assert(subscription_record.len() == 1, 'record should have length 1');

dispatcher.cancel_subscription(account_id);
let subscription = dispatcher.get_user_subscription(account_id);
assert(subscription.id == 1, 'Subscription ID should be 1');
assert(subscription.subscription_type == PlanType::YEARLY, 'Plan type should be YEARLY');
assert(subscription.status == SubscriptionStatus::Cancelled, 'Plan status should be cancelled');
let subscription_record = dispatcher.get_user_subscription_record(account_id);
assert(subscription_record.len() == 1, 'record should have length 1');

dispatcher.renew_subscription(account_id);
let subscription = dispatcher.get_user_subscription(account_id);
assert(subscription.id == 1, 'Subscription ID should be 1');
assert(subscription.subscription_type == PlanType::YEARLY, 'Plan type should be YEARLY');
assert(subscription.status == SubscriptionStatus::Active, 'Plan status should be Active');
let subscription_record = dispatcher.get_user_subscription_record(account_id);
assert(subscription_record.len() == 1, 'record should have length 1');
}
Loading