diff --git a/contracts/cntr/Cargo.toml b/contracts/cntr/Cargo.toml index 342774c..9420c90 100644 --- a/contracts/cntr/Cargo.toml +++ b/contracts/cntr/Cargo.toml @@ -1,5 +1,7 @@ [package] name = "cntr" +version = "0.1.0" +edition = "2021" version = "0.0.0" version = "0.1.0" edition = "2021" diff --git a/contracts/cntr/src/escrow_refund.rs b/contracts/cntr/src/escrow_refund.rs new file mode 100644 index 0000000..a95daf3 --- /dev/null +++ b/contracts/cntr/src/escrow_refund.rs @@ -0,0 +1,53 @@ +pub fn calculate_refund_amount( + booking_amount_stroops: i128, + cancellation_hours_before_start: u64, +) -> i128 { + if cancellation_hours_before_start >= 48 { + booking_amount_stroops + } else if cancellation_hours_before_start >= 24 { + booking_amount_stroops / 2 + } else { + 0 + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn full_refund_at_48h() { + assert_eq!(calculate_refund_amount(1_000_000, 48), 1_000_000); + } + + #[test] + fn full_refund_beyond_48h() { + assert_eq!(calculate_refund_amount(1_000_000, 72), 1_000_000); + } + + #[test] + fn half_refund_at_24h() { + assert_eq!(calculate_refund_amount(1_000_000, 24), 500_000); + } + + #[test] + fn half_refund_at_47h() { + assert_eq!(calculate_refund_amount(1_000_000, 47), 500_000); + } + + #[test] + fn no_refund_at_23h() { + assert_eq!(calculate_refund_amount(1_000_000, 23), 0); + } + + #[test] + fn no_refund_at_0h() { + assert_eq!(calculate_refund_amount(1_000_000, 0), 0); + } + + #[test] + fn integer_division_no_float() { + // odd amount: 1_000_001 / 2 = 500_000 (integer division) + assert_eq!(calculate_refund_amount(1_000_001, 24), 500_000); + } +} diff --git a/contracts/cntr/src/escrow_release.rs b/contracts/cntr/src/escrow_release.rs new file mode 100644 index 0000000..1e4fefc --- /dev/null +++ b/contracts/cntr/src/escrow_release.rs @@ -0,0 +1,54 @@ +pub fn validate_escrow_release( + booking_status: &str, + dispute_window_expired: bool, +) -> Result<(), &'static str> { + if booking_status != "COMPLETED" { + return Err("Booking not completed"); + } + if !dispute_window_expired { + return Err("Dispute window still active"); + } + Ok(()) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn ok_when_completed_and_window_expired() { + assert_eq!(validate_escrow_release("COMPLETED", true), Ok(())); + } + + #[test] + fn err_when_not_completed() { + assert_eq!( + validate_escrow_release("PENDING", true), + Err("Booking not completed") + ); + } + + #[test] + fn err_when_dispute_window_active() { + assert_eq!( + validate_escrow_release("COMPLETED", false), + Err("Dispute window still active") + ); + } + + #[test] + fn err_when_both_conditions_fail_returns_not_completed() { + assert_eq!( + validate_escrow_release("PENDING", false), + Err("Booking not completed") + ); + } + + #[test] + fn err_for_cancelled_status() { + assert_eq!( + validate_escrow_release("CANCELLED", true), + Err("Booking not completed") + ); + } +} diff --git a/contracts/cntr/src/lib.rs b/contracts/cntr/src/lib.rs index ca4756e..cedee37 100644 --- a/contracts/cntr/src/lib.rs +++ b/contracts/cntr/src/lib.rs @@ -1,3 +1,7 @@ +pub mod subscription_expiry; +pub mod member_tier; +pub mod escrow_release; +pub mod escrow_refund; pub mod credit_deduction; pub mod referral_reward; pub mod role_checker; diff --git a/contracts/cntr/src/member_tier.rs b/contracts/cntr/src/member_tier.rs new file mode 100644 index 0000000..d8d0366 --- /dev/null +++ b/contracts/cntr/src/member_tier.rs @@ -0,0 +1,64 @@ +#[derive(Debug, PartialEq, Clone)] +pub enum MemberTier { + Bronze, + Silver, + Gold, + Platinum, +} + +pub fn calculate_tier(total_bookings: u32, total_spend_stroops: i128) -> MemberTier { + if total_bookings >= 100 || total_spend_stroops >= 100_000_000 { + MemberTier::Platinum + } else if total_bookings >= 30 || total_spend_stroops >= 20_000_000 { + MemberTier::Gold + } else if total_bookings >= 10 || total_spend_stroops >= 5_000_000 { + MemberTier::Silver + } else { + MemberTier::Bronze + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn bronze_by_default() { + assert_eq!(calculate_tier(0, 0), MemberTier::Bronze); + } + + #[test] + fn silver_at_10_bookings() { + assert_eq!(calculate_tier(10, 0), MemberTier::Silver); + } + + #[test] + fn silver_by_spend() { + assert_eq!(calculate_tier(0, 5_000_000), MemberTier::Silver); + } + + #[test] + fn gold_at_30_bookings() { + assert_eq!(calculate_tier(30, 0), MemberTier::Gold); + } + + #[test] + fn gold_by_spend() { + assert_eq!(calculate_tier(0, 20_000_000), MemberTier::Gold); + } + + #[test] + fn platinum_at_100_bookings() { + assert_eq!(calculate_tier(100, 0), MemberTier::Platinum); + } + + #[test] + fn platinum_by_spend() { + assert_eq!(calculate_tier(0, 100_000_000), MemberTier::Platinum); + } + + #[test] + fn below_silver_threshold() { + assert_eq!(calculate_tier(9, 4_999_999), MemberTier::Bronze); + } +} diff --git a/contracts/cntr/src/subscription_expiry.rs b/contracts/cntr/src/subscription_expiry.rs new file mode 100644 index 0000000..2510362 --- /dev/null +++ b/contracts/cntr/src/subscription_expiry.rs @@ -0,0 +1,45 @@ +pub fn is_subscription_expired(expiry_timestamp: u64, current_timestamp: u64) -> bool { + current_timestamp >= expiry_timestamp +} + +pub fn days_until_expiry(expiry_timestamp: u64, current_timestamp: u64) -> i64 { + let diff = expiry_timestamp as i64 - current_timestamp as i64; + diff / 86400 +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn expired_when_current_past_expiry() { + assert!(is_subscription_expired(100, 200)); + } + + #[test] + fn not_expired_when_current_before_expiry() { + assert!(!is_subscription_expired(200, 100)); + } + + #[test] + fn expired_at_boundary() { + assert!(is_subscription_expired(100, 100)); + } + + #[test] + fn days_until_positive_when_not_expired() { + // 10 days in the future + assert_eq!(days_until_expiry(864000, 0), 10); + } + + #[test] + fn days_until_negative_when_expired() { + // expired 1 day ago + assert_eq!(days_until_expiry(0, 86400), -1); + } + + #[test] + fn days_until_zero_at_boundary() { + assert_eq!(days_until_expiry(100, 100), 0); + } +}