diff --git a/contract/contracts/subscription/src/lib.rs b/contract/contracts/subscription/src/lib.rs index feb241a..886216f 100644 --- a/contract/contracts/subscription/src/lib.rs +++ b/contract/contracts/subscription/src/lib.rs @@ -95,11 +95,11 @@ impl MyfansContract { token_client.transfer(&fan, &fee_recipient, &fee); } - let expiry = env.ledger().timestamp() + (plan.interval_days as u64 * 86400); + let expiry = env.ledger().sequence() + (plan.interval_days * 17280); let sub = Subscription { fan: fan.clone(), plan_id, - expiry, + expiry: expiry as u64, }; env.storage() .instance() @@ -114,12 +114,68 @@ impl MyfansContract { .instance() .get::(&DataKey::Sub(fan, creator)) { - sub.expiry > env.ledger().timestamp() + env.ledger().sequence() <= sub.expiry as u32 } else { false } } + pub fn extend_subscription( + env: Env, + fan: Address, + creator: Address, + extra_ledgers: u32, + token: Address, + ) { + fan.require_auth(); + + let sub: Subscription = env + .storage() + .instance() + .get(&DataKey::Sub(fan.clone(), creator.clone())) + .expect("subscription not found"); + + if env.ledger().sequence() > sub.expiry as u32 { + panic!("subscription expired"); + } + + let plan: Plan = env + .storage() + .instance() + .get(&DataKey::Plan(sub.plan_id)) + .unwrap(); + + let fee_bps: u32 = env.storage().instance().get(&DataKey::FeeBps).unwrap_or(0); + let fee_recipient: Address = env + .storage() + .instance() + .get(&DataKey::FeeRecipient) + .unwrap(); + + let fee = (plan.amount * fee_bps as i128) / 10000; + let creator_amount = plan.amount - fee; + + let token_client = token::Client::new(&env, &token); + token_client.transfer(&fan, &creator, &creator_amount); + if fee > 0 { + token_client.transfer(&fan, &fee_recipient, &fee); + } + + let new_expiry = sub.expiry + extra_ledgers as u64; + let updated_sub = Subscription { + fan: fan.clone(), + plan_id: sub.plan_id, + expiry: new_expiry, + }; + + env.storage() + .instance() + .set(&DataKey::Sub(fan.clone(), creator), &updated_sub); + + env.events() + .publish((Symbol::new(&env, "extended"), sub.plan_id), fan); + } + pub fn cancel(env: Env, fan: Address, creator: Address) { fan.require_auth(); env.storage() diff --git a/contract/contracts/subscription/src/test.rs b/contract/contracts/subscription/src/test.rs index e2166a3..b967662 100644 --- a/contract/contracts/subscription/src/test.rs +++ b/contract/contracts/subscription/src/test.rs @@ -43,7 +43,7 @@ fn test_subscribe_full_flow() { assert_eq!(plan_id, 1); // Subscribe calls token transfer, so it will deduct from fan - client.subscribe(&fan, &plan_id); + client.subscribe(&fan, &plan_id, &token.address); // Check balances // Fan paid 1000, should have 9000 @@ -74,7 +74,7 @@ fn test_subscribe_insufficient_balance_reverts() { let plan_id = client.create_plan(&creator, &token.address, &1000, &30); // This should panic due to token transfer failure automatically mapped inside Soroban - client.subscribe(&fan, &plan_id); + client.subscribe(&fan, &plan_id, &token.address); } #[test] @@ -92,7 +92,7 @@ fn test_platform_fee_zero() { token_admin.mint(&fan, &10000); let plan_id = client.create_plan(&creator, &token.address, &1000, &30); - client.subscribe(&fan, &plan_id); + client.subscribe(&fan, &plan_id, &token.address); // Fee is 0%. Creator gets all 1000. assert_eq!(token.balance(&fee_recipient), 0); @@ -110,10 +110,105 @@ fn test_cancel_subscription() { token_admin.mint(&fan, &10000); let plan_id = client.create_plan(&creator, &token.address, &1000, &30); - client.subscribe(&fan, &plan_id); + client.subscribe(&fan, &plan_id, &token.address); assert!(client.is_subscriber(&fan, &creator)); client.cancel(&fan, &creator); assert!(!client.is_subscriber(&fan, &creator)); } + +#[test] +fn test_is_subscribed_false_after_expiry() { + let (env, client, admin, token, token_admin) = setup_test(); + let fee_recipient = Address::generate(&env); + client.init(&admin, &0, &fee_recipient); + + let creator = Address::generate(&env); + let fan = Address::generate(&env); + + token_admin.mint(&fan, &10000); + let plan_id = client.create_plan(&creator, &token.address, &1000, &1); + client.subscribe(&fan, &plan_id, &token.address); + + assert!(client.is_subscriber(&fan, &creator)); + + env.ledger().with_mut(|li| { + li.sequence_number += 17281; + }); + + assert!(!client.is_subscriber(&fan, &creator)); +} + +#[test] +fn test_extend_updates_expiry() { + let (env, client, admin, token, token_admin) = setup_test(); + let fee_recipient = Address::generate(&env); + client.init(&admin, &0, &fee_recipient); + + let creator = Address::generate(&env); + let fan = Address::generate(&env); + + token_admin.mint(&fan, &20000); + let plan_id = client.create_plan(&creator, &token.address, &1000, &1); + client.subscribe(&fan, &plan_id, &token.address); + + let initial_ledger = env.ledger().sequence(); + let expected_expiry = initial_ledger + 17280; + + env.ledger().with_mut(|li| { + li.sequence_number += 10000; + }); + + assert!(client.is_subscriber(&fan, &creator)); + + client.extend_subscription(&fan, &creator, &17280, &token.address); + + env.ledger().with_mut(|li| { + li.sequence_number = expected_expiry + 1; + }); + + assert!(client.is_subscriber(&fan, &creator)); +} + +#[test] +fn test_extend_requires_payment() { + let (env, client, admin, token, token_admin) = setup_test(); + let fee_recipient = Address::generate(&env); + client.init(&admin, &0, &fee_recipient); + + let creator = Address::generate(&env); + let fan = Address::generate(&env); + + token_admin.mint(&fan, &20000); + let plan_id = client.create_plan(&creator, &token.address, &1000, &1); + client.subscribe(&fan, &plan_id, &token.address); + + assert_eq!(token.balance(&creator), 1000); + + client.extend_subscription(&fan, &creator, &17280, &token.address); + + assert_eq!(token.balance(&creator), 2000); + assert_eq!(token.balance(&fan), 18000); +} + +#[test] +#[should_panic(expected = "subscription expired")] +fn test_extend_fails_if_expired() { + let (env, client, admin, token, token_admin) = setup_test(); + let fee_recipient = Address::generate(&env); + client.init(&admin, &0, &fee_recipient); + + let creator = Address::generate(&env); + let fan = Address::generate(&env); + + token_admin.mint(&fan, &20000); + let plan_id = client.create_plan(&creator, &token.address, &1000, &1); + client.subscribe(&fan, &plan_id, &token.address); + + env.ledger().with_mut(|li| { + li.sequence_number += 17281; + }); + + client.extend_subscription(&fan, &creator, &17280, &token.address); +}