Skip to content

Commit a0795c8

Browse files
Merge pull request #57 from ikemHood/feature/graceful-session-extensions-33
feat: implement session top-up functionality to extend booking duration
2 parents 1a4ddef + 1e90aeb commit a0795c8

4 files changed

Lines changed: 157 additions & 0 deletions

File tree

contracts/payment-vault-contract/src/contract.rs

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,69 @@ pub fn book_session(
134134
Ok(booking_id)
135135
}
136136

137+
pub fn top_up_session(
138+
env: &Env,
139+
user: &Address,
140+
booking_id: u64,
141+
additional_duration: u64,
142+
) -> Result<(), VaultError> {
143+
if storage::is_paused(env) {
144+
return Err(VaultError::ContractPaused);
145+
}
146+
147+
// Require authorization from the user
148+
user.require_auth();
149+
150+
// Get booking and verify it exists
151+
let mut booking = storage::get_booking(env, booking_id).ok_or(VaultError::BookingNotFound)?;
152+
153+
// Verify the caller is the booking owner
154+
if booking.user != *user {
155+
return Err(VaultError::NotAuthorized);
156+
}
157+
158+
// Verify booking is in Pending status
159+
if booking.status != BookingStatus::Pending {
160+
return Err(VaultError::BookingNotPending);
161+
}
162+
163+
// Calculate extra cost
164+
let extra_cost = booking
165+
.rate_per_second
166+
.checked_mul(additional_duration as i128)
167+
.ok_or(VaultError::Overflow)?;
168+
169+
if extra_cost <= 0 {
170+
return Err(VaultError::InvalidAmount);
171+
}
172+
173+
// Get the token contract
174+
let token_address = storage::get_token(env);
175+
let token_client = token::Client::new(env, &token_address);
176+
177+
// Transfer extra tokens from user to this contract
178+
let contract_address = env.current_contract_address();
179+
token_client.transfer(user, &contract_address, &extra_cost);
180+
181+
// Update booking
182+
booking.total_deposit = booking
183+
.total_deposit
184+
.checked_add(extra_cost)
185+
.ok_or(VaultError::Overflow)?;
186+
booking.max_duration = booking
187+
.max_duration
188+
.checked_add(additional_duration)
189+
.ok_or(VaultError::Overflow)?;
190+
191+
// Save booking
192+
storage::save_booking(env, &booking);
193+
194+
// Emit event
195+
events::session_topped_up(env, booking_id, additional_duration, extra_cost);
196+
197+
Ok(())
198+
}
199+
137200
pub fn finalize_session(
138201
env: &Env,
139202
booking_id: u64,

contracts/payment-vault-contract/src/events.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,3 +68,9 @@ pub fn oracle_updated(env: &Env, old_oracle: &Address, new_oracle: &Address) {
6868
env.events()
6969
.publish(topics, (old_oracle.clone(), new_oracle.clone()));
7070
}
71+
72+
/// Emitted when a user tops up a pending session
73+
pub fn session_topped_up(env: &Env, booking_id: u64, additional_duration: u64, extra_cost: i128) {
74+
let topics = (symbol_short!("top_up"), booking_id);
75+
env.events().publish(topics, (additional_duration, extra_cost));
76+
}

contracts/payment-vault-contract/src/lib.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,17 @@ impl PaymentVaultContract {
7171
contract::book_session(&env, &user, &expert, max_duration)
7272
}
7373

74+
/// Add more time to a live (or pending) session without disconnecting.
75+
/// Deducts corresponding tokens from the user based on the expert's rate.
76+
pub fn top_up_session(
77+
env: Env,
78+
user: Address,
79+
booking_id: u64,
80+
additional_duration: u64,
81+
) -> Result<(), VaultError> {
82+
contract::top_up_session(&env, &user, booking_id, additional_duration)
83+
}
84+
7485
/// Finalize a session (Oracle-only).
7586
/// Calculates payments based on actual duration and processes refunds.
7687
pub fn finalize_session(

contracts/payment-vault-contract/src/test.rs

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1403,3 +1403,80 @@ fn test_expert_pagination_50_bookings() {
14031403
let tail = client.get_expert_bookings(&expert, &40, &20);
14041404
assert_eq!(tail.len(), 10); // only 10 left from index 40 to 49
14051405
}
1406+
1407+
#[test]
1408+
fn test_top_up_session_success() {
1409+
let env = Env::default();
1410+
env.mock_all_auths();
1411+
1412+
let admin = Address::generate(&env);
1413+
let user = Address::generate(&env);
1414+
let expert = Address::generate(&env);
1415+
let oracle = Address::generate(&env);
1416+
let registry = create_mock_registry(&env);
1417+
1418+
let token_admin = Address::generate(&env);
1419+
let token = create_token_contract(&env, &token_admin);
1420+
token.mint(&user, &100_000);
1421+
1422+
let client = create_client(&env);
1423+
client.init(&admin, &token.address, &oracle, &registry);
1424+
1425+
let rate_per_second = 10_i128;
1426+
let initial_duration = 1800_u64; // 30 mins
1427+
let additional_duration = 900_u64; // 15 mins
1428+
1429+
let booking_id = {
1430+
client.set_my_rate(&expert, &rate_per_second);
1431+
client.book_session(&user, &expert, &initial_duration)
1432+
};
1433+
1434+
let initial_deposit = rate_per_second * (initial_duration as i128); // 18,000
1435+
assert_eq!(token.balance(&client.address), initial_deposit);
1436+
1437+
// Top up 15 mins
1438+
let extra_cost = rate_per_second * (additional_duration as i128); // 9,000
1439+
let result = client.try_top_up_session(&user, &booking_id, &additional_duration);
1440+
assert!(result.is_ok());
1441+
1442+
// Verify balances
1443+
let expected_total_deposit = initial_deposit + extra_cost;
1444+
assert_eq!(token.balance(&client.address), expected_total_deposit);
1445+
1446+
// Verify booking record
1447+
let booking = client.get_booking(&booking_id).unwrap();
1448+
assert_eq!(booking.max_duration, initial_duration + additional_duration);
1449+
assert_eq!(booking.total_deposit, expected_total_deposit);
1450+
}
1451+
1452+
#[test]
1453+
fn test_top_up_wrong_user_fails() {
1454+
let env = Env::default();
1455+
env.mock_all_auths();
1456+
1457+
let admin = Address::generate(&env);
1458+
let user = Address::generate(&env);
1459+
let other_user = Address::generate(&env);
1460+
let expert = Address::generate(&env);
1461+
let oracle = Address::generate(&env);
1462+
let registry = create_mock_registry(&env);
1463+
1464+
let token_admin = Address::generate(&env);
1465+
let token = create_token_contract(&env, &token_admin);
1466+
token.mint(&user, &40_000);
1467+
token.mint(&other_user, &40_000);
1468+
1469+
let client = create_client(&env);
1470+
client.init(&admin, &token.address, &oracle, &registry);
1471+
1472+
let rate_per_second = 10_i128;
1473+
let initial_duration = 1800_u64;
1474+
1475+
let booking_id = {
1476+
client.set_my_rate(&expert, &rate_per_second);
1477+
client.book_session(&user, &expert, &initial_duration)
1478+
};
1479+
1480+
let result = client.try_top_up_session(&other_user, &booking_id, &900);
1481+
assert!(result.is_err());
1482+
}

0 commit comments

Comments
 (0)