diff --git a/contracts/payment-vault-contract/src/contract.rs b/contracts/payment-vault-contract/src/contract.rs index e9b10ac..3b4196f 100644 --- a/contracts/payment-vault-contract/src/contract.rs +++ b/contracts/payment-vault-contract/src/contract.rs @@ -134,6 +134,69 @@ pub fn book_session( Ok(booking_id) } +pub fn top_up_session( + env: &Env, + user: &Address, + booking_id: u64, + additional_duration: u64, +) -> Result<(), VaultError> { + if storage::is_paused(env) { + return Err(VaultError::ContractPaused); + } + + // Require authorization from the user + user.require_auth(); + + // Get booking and verify it exists + let mut booking = storage::get_booking(env, booking_id).ok_or(VaultError::BookingNotFound)?; + + // Verify the caller is the booking owner + if booking.user != *user { + return Err(VaultError::NotAuthorized); + } + + // Verify booking is in Pending status + if booking.status != BookingStatus::Pending { + return Err(VaultError::BookingNotPending); + } + + // Calculate extra cost + let extra_cost = booking + .rate_per_second + .checked_mul(additional_duration as i128) + .ok_or(VaultError::Overflow)?; + + if extra_cost <= 0 { + return Err(VaultError::InvalidAmount); + } + + // Get the token contract + let token_address = storage::get_token(env); + let token_client = token::Client::new(env, &token_address); + + // Transfer extra tokens from user to this contract + let contract_address = env.current_contract_address(); + token_client.transfer(user, &contract_address, &extra_cost); + + // Update booking + booking.total_deposit = booking + .total_deposit + .checked_add(extra_cost) + .ok_or(VaultError::Overflow)?; + booking.max_duration = booking + .max_duration + .checked_add(additional_duration) + .ok_or(VaultError::Overflow)?; + + // Save booking + storage::save_booking(env, &booking); + + // Emit event + events::session_topped_up(env, booking_id, additional_duration, extra_cost); + + Ok(()) +} + pub fn finalize_session( env: &Env, booking_id: u64, diff --git a/contracts/payment-vault-contract/src/events.rs b/contracts/payment-vault-contract/src/events.rs index 529b0a5..e93538a 100644 --- a/contracts/payment-vault-contract/src/events.rs +++ b/contracts/payment-vault-contract/src/events.rs @@ -68,3 +68,9 @@ pub fn oracle_updated(env: &Env, old_oracle: &Address, new_oracle: &Address) { env.events() .publish(topics, (old_oracle.clone(), new_oracle.clone())); } + +/// Emitted when a user tops up a pending session +pub fn session_topped_up(env: &Env, booking_id: u64, additional_duration: u64, extra_cost: i128) { + let topics = (symbol_short!("top_up"), booking_id); + env.events().publish(topics, (additional_duration, extra_cost)); +} diff --git a/contracts/payment-vault-contract/src/lib.rs b/contracts/payment-vault-contract/src/lib.rs index e2f31b9..76d10e2 100644 --- a/contracts/payment-vault-contract/src/lib.rs +++ b/contracts/payment-vault-contract/src/lib.rs @@ -71,6 +71,17 @@ impl PaymentVaultContract { contract::book_session(&env, &user, &expert, max_duration) } + /// Add more time to a live (or pending) session without disconnecting. + /// Deducts corresponding tokens from the user based on the expert's rate. + pub fn top_up_session( + env: Env, + user: Address, + booking_id: u64, + additional_duration: u64, + ) -> Result<(), VaultError> { + contract::top_up_session(&env, &user, booking_id, additional_duration) + } + /// Finalize a session (Oracle-only). /// Calculates payments based on actual duration and processes refunds. pub fn finalize_session( diff --git a/contracts/payment-vault-contract/src/test.rs b/contracts/payment-vault-contract/src/test.rs index 67337f3..b9fa490 100644 --- a/contracts/payment-vault-contract/src/test.rs +++ b/contracts/payment-vault-contract/src/test.rs @@ -1403,3 +1403,80 @@ fn test_expert_pagination_50_bookings() { let tail = client.get_expert_bookings(&expert, &40, &20); assert_eq!(tail.len(), 10); // only 10 left from index 40 to 49 } + +#[test] +fn test_top_up_session_success() { + let env = Env::default(); + env.mock_all_auths(); + + let admin = Address::generate(&env); + let user = Address::generate(&env); + let expert = Address::generate(&env); + let oracle = Address::generate(&env); + let registry = create_mock_registry(&env); + + let token_admin = Address::generate(&env); + let token = create_token_contract(&env, &token_admin); + token.mint(&user, &100_000); + + let client = create_client(&env); + client.init(&admin, &token.address, &oracle, ®istry); + + let rate_per_second = 10_i128; + let initial_duration = 1800_u64; // 30 mins + let additional_duration = 900_u64; // 15 mins + + let booking_id = { + client.set_my_rate(&expert, &rate_per_second); + client.book_session(&user, &expert, &initial_duration) + }; + + let initial_deposit = rate_per_second * (initial_duration as i128); // 18,000 + assert_eq!(token.balance(&client.address), initial_deposit); + + // Top up 15 mins + let extra_cost = rate_per_second * (additional_duration as i128); // 9,000 + let result = client.try_top_up_session(&user, &booking_id, &additional_duration); + assert!(result.is_ok()); + + // Verify balances + let expected_total_deposit = initial_deposit + extra_cost; + assert_eq!(token.balance(&client.address), expected_total_deposit); + + // Verify booking record + let booking = client.get_booking(&booking_id).unwrap(); + assert_eq!(booking.max_duration, initial_duration + additional_duration); + assert_eq!(booking.total_deposit, expected_total_deposit); +} + +#[test] +fn test_top_up_wrong_user_fails() { + let env = Env::default(); + env.mock_all_auths(); + + let admin = Address::generate(&env); + let user = Address::generate(&env); + let other_user = Address::generate(&env); + let expert = Address::generate(&env); + let oracle = Address::generate(&env); + let registry = create_mock_registry(&env); + + let token_admin = Address::generate(&env); + let token = create_token_contract(&env, &token_admin); + token.mint(&user, &40_000); + token.mint(&other_user, &40_000); + + let client = create_client(&env); + client.init(&admin, &token.address, &oracle, ®istry); + + let rate_per_second = 10_i128; + let initial_duration = 1800_u64; + + let booking_id = { + client.set_my_rate(&expert, &rate_per_second); + client.book_session(&user, &expert, &initial_duration) + }; + + let result = client.try_top_up_session(&other_user, &booking_id, &900); + assert!(result.is_err()); +}