AdvancedSubscriptionSystem

Description:

Smart contract deployed on Ethereum.

Blockchain: Ethereum

Source Code: View Code On The Blockchain

Solidity Source Code:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

// Interface for interaction with ReferralSystem
interface IReferralSystem {
    function processReferralReward(address referrer, address user, uint256 planId, uint256 purchaseId, uint256 originalPrice) external;
    function getReferralDiscount(address) external view returns (uint8);
}

contract AdvancedSubscriptionSystem {
    address payable public owner;
    bool public paused;
    
    // Address of the ReferralSystem contract
    address public referralSystemAddress;
    
    enum CorePlanType { Daily, Monthly, Quarterly, Yearly }
    
    struct Plan {
        string name;
        uint256 price;
        uint256 duration;
        bool isActive;
        uint256 createdAt;
    }
    
    mapping(uint8 => Plan) public corePlans;
    
    struct UserSubscription {
        uint256 expiryTime;
        uint256[] purchaseIds;
    }
    
    struct Purchase {
        address user;
        uint256 planId;
        uint256 pricePaid;
        uint256 purchaseTime;
        address referrer;
        bytes32 couponCode;
        uint256 subscriptionEnd;
    }
    
    mapping(uint256 => Purchase) public purchases;
    uint256 private nextPurchaseId = 1;
    
    mapping(address => UserSubscription) public userSubscriptions;
    
    struct DiscountCoupon {
        string title;
        uint8 discountPercentage;
        uint16 usageLimit;
        uint16 usageCount;
        uint256 startTime;
        uint256 endTime;
        bool isPlanRestricted;
        bool isUserRestricted;
        bool isActive;
    }
    
    mapping(bytes32 => mapping(uint256 => bool)) public couponPlanRestrictions;
    
    mapping(bytes32 => mapping(address => bool)) public couponUserRestrictions;
    
    mapping(bytes32 => DiscountCoupon) public discountCoupons;
    
    event CorePlanUpdated(uint8 indexed planType, uint256 oldPrice, uint256 newPrice, uint256 oldDuration, uint256 newDuration);
    event PlanStatusChanged(uint8 indexed planId, bool isActive);
    
    event SubscriptionPurchased(
        address indexed user, 
        uint256 indexed planId, 
        uint256 purchaseId,
        uint256 expiryTime, 
        uint256 amountPaid
    );
    
    event DiscountCouponCreated(
        bytes32 indexed couponCode, 
        string title,
        uint8 discountPercentage,
        uint16 usageLimit
    );
    
    event DiscountCouponUsed(
        address indexed user, 
        bytes32 indexed couponCode, 
        uint8 discountPercentage,
        uint256 purchaseId
    );
    
    event DiscountCouponUpdated(
        bytes32 indexed couponCode,
        string title,
        uint8 discountPercentage,
        uint16 usageLimit,
        bool isActive
    );
    
    event EmergencyStatusChanged(bool paused);
    event WithdrawnByOwner(uint256 amount);
    event AccessRevoked(address indexed user);
    event CouponDeleted(bytes32 indexed couponCode);
    event ReferralSystemAddressSet(address indexed newAddress);
    
    modifier onlyOwner() {
        require(msg.sender == owner, "Only owner can call this function");
        _;
    }
    
    modifier whenNotPaused() {
        require(!paused, "Contract is paused");
        _;
    }
    
    modifier hasValidSubscription() {
        require(
            userSubscriptions[msg.sender].expiryTime > block.timestamp, 
            "No valid subscription"
        );
        _;
    }
    
    // ============= Constructor & Admin Functions =============
    /**
     * @dev Constructor initializes the contract with default plans and settings
     */
    constructor() {
        owner = payable(msg.sender);
        
        corePlans[uint8(CorePlanType.Daily)] = Plan({
            name: "Daily",
            price: 1000000000000000,
            duration: 1 days,
            isActive: true,
            createdAt: block.timestamp
        });
        
        corePlans[uint8(CorePlanType.Monthly)] = Plan({
            name: "Monthly",
            price: 7000000000000000,
            duration: 30 days,
            isActive: true,
            createdAt: block.timestamp
        });
        
        corePlans[uint8(CorePlanType.Quarterly)] = Plan({
            name: "Quarterly",
            price: 14000000000000000,
            duration: 90 days,
            isActive: true,
            createdAt: block.timestamp
        });
        
        corePlans[uint8(CorePlanType.Yearly)] = Plan({
            name: "Yearly",
            price: 100000000000000000,
            duration: 365 days,
            isActive: true,
            createdAt: block.timestamp
        });
    }
    
    /**
     * @dev Change contract ownership
     * @param newOwner Address of the new owner
     */
    function transferOwnership(address payable newOwner) external onlyOwner {
        require(newOwner != address(0), "New owner cannot be zero address");
        owner = newOwner;
    }
    
    /**
     * @dev Toggle emergency pause status
     * @param _paused New pause status
     */
    function setPaused(bool _paused) external onlyOwner {
        paused = _paused;
        emit EmergencyStatusChanged(_paused);
    }
    
    /**
     * @dev Withdraw contract balance (owner only)
     */
    function withdraw() external onlyOwner {
        uint256 balance = address(this).balance;
        require(balance > 0, "No balance to withdraw");
        
        (bool success, ) = owner.call{value: balance}("");
        require(success, "Withdrawal failed");
        
        emit WithdrawnByOwner(balance);
    }
    
    /**
     * @dev Revoke user access (owner only)
     * @param user Address of the user
     */
    function revokeAccess(address user) external onlyOwner {
        userSubscriptions[user].expiryTime = block.timestamp;
        emit AccessRevoked(user);
    }
    
    /**
     * @dev Set ReferralSystem contract address
     * @param _referralSystemAddress Address of the ReferralSystem contract
     */
    function setReferralSystemAddress(address _referralSystemAddress) external onlyOwner {
        require(_referralSystemAddress != address(0), "Invalid address");
        referralSystemAddress = _referralSystemAddress;
        emit ReferralSystemAddressSet(_referralSystemAddress);
    }
    
    // ============= Plan Management Functions =============
    /**
     * @dev Update core plan price and/or duration
     * @param planType Core plan type
     * @param newPrice New price in wei
     * @param newDuration New duration in seconds
     */
    function updateCorePlan(CorePlanType planType, uint256 newPrice, uint256 newDuration) 
        external 
        onlyOwner 
    {
        require(newPrice > 0, "Price must be greater than 0");
        require(newDuration > 0, "Duration must be greater than 0");
        
        uint8 planTypeId = uint8(planType);
        Plan storage plan = corePlans[planTypeId];
        
        uint256 oldPrice = plan.price;
        uint256 oldDuration = plan.duration;
        
        plan.price = newPrice;
        plan.duration = newDuration;
        
        emit CorePlanUpdated(planTypeId, oldPrice, newPrice, oldDuration, newDuration);
    }
    
    /**
     * @dev Toggle activation status of a core plan
     * @param planType Core plan type
     * @param isActive New active status
     */
    function setCorePlanStatus(CorePlanType planType, bool isActive) 
        external 
        onlyOwner 
    {
        uint8 planTypeId = uint8(planType);
        corePlans[planTypeId].isActive = isActive;
        
        emit PlanStatusChanged(planTypeId, isActive);
    }
    
    // ============= Discount Coupon Management Functions =============
    /**
     * @dev Create a new discount coupon
     * @param couponCode Unique coupon code
     * @param title Descriptive title for the coupon
     * @param discountPercentage Discount percentage (10-100)
     * @param usageLimit Maximum number of uses (0 for unlimited)
     * @param startTime Start time for coupon validity
     * @param endTime End time for coupon validity (0 for no expiry)
     */
    function createDiscountCoupon(
        bytes32 couponCode,
        string calldata title,
        uint8 discountPercentage,
        uint16 usageLimit,
        uint256 startTime,
        uint256 endTime
    ) 
        external 
        onlyOwner 
    {
        require(discountPercentage >= 10 && discountPercentage <= 100, "Discount must be between 10-100%");
        require(discountCoupons[couponCode].discountPercentage == 0, "Coupon code already exists");
        require(bytes(title).length > 0, "Title cannot be empty");
        
        if (endTime > 0) {
            require(endTime > startTime, "End time must be after start time");
        }
        
        if (startTime == 0) {
            startTime = block.timestamp;
        }
        
        discountCoupons[couponCode] = DiscountCoupon({
            title: title,
            discountPercentage: discountPercentage,
            usageLimit: usageLimit,
            usageCount: 0,
            startTime: startTime,
            endTime: endTime,
            isPlanRestricted: false,
            isUserRestricted: false,
            isActive: true
        });
        
        emit DiscountCouponCreated(couponCode, title, discountPercentage, usageLimit);
    }
    
    /**
     * @dev Update an existing discount coupon
     * @param couponCode Coupon code to update
     * @param title New title
     * @param discountPercentage New discount percentage
     * @param usageLimit New usage limit
     * @param isActive New active status
     */
    function updateDiscountCoupon(
        bytes32 couponCode,
        string calldata title,
        uint8 discountPercentage,
        uint16 usageLimit,
        bool isActive
    ) 
        external 
        onlyOwner 
    {
        require(discountCoupons[couponCode].discountPercentage > 0, "Coupon does not exist");
        require(discountPercentage >= 10 && discountPercentage <= 100, "Discount must be between 10-100%");
        require(bytes(title).length > 0, "Title cannot be empty");
        
        DiscountCoupon storage coupon = discountCoupons[couponCode];
        
        coupon.title = title;
        coupon.discountPercentage = discountPercentage;
        coupon.usageLimit = usageLimit;
        coupon.isActive = isActive;
        
        emit DiscountCouponUpdated(couponCode, title, discountPercentage, usageLimit, isActive);
    }
    
    /**
     * @dev Set plan restrictions for a coupon
     * @param couponCode Coupon code
     * @param planIds Array of plan IDs
     */
    function setCouponPlanRestrictions(
        bytes32 couponCode,
        uint256[] calldata planIds
    ) 
        external 
        onlyOwner 
    {
        require(discountCoupons[couponCode].discountPercentage > 0, "Coupon does not exist");
        
        DiscountCoupon storage coupon = discountCoupons[couponCode];
        coupon.isPlanRestricted = planIds.length > 0;
        
        for (uint256 i = 0; i < planIds.length; i++) {
            // Validate that the plans exist before adding restrictions
            require(planIds[i] < 4, "Invalid core plan ID");
            require(bytes(corePlans[uint8(planIds[i])].name).length > 0, "Core plan does not exist");
            
            // Set the restriction
            couponPlanRestrictions[couponCode][planIds[i]] = true;
        }
    }
    
    /**
     * @dev Set user restrictions for a coupon
     * @param couponCode Coupon code
     * @param users Array of user addresses
     */
    function setCouponUserRestrictions(
        bytes32 couponCode,
        address[] calldata users
    ) 
        external 
        onlyOwner 
    {
        require(discountCoupons[couponCode].discountPercentage > 0, "Coupon does not exist");
        
        DiscountCoupon storage coupon = discountCoupons[couponCode];
        coupon.isUserRestricted = users.length > 0;
        
        for (uint256 i = 0; i < users.length; i++) {
            couponUserRestrictions[couponCode][users[i]] = true;
        }
    }
    
    /**
     * @dev Delete a discount coupon
     * @param couponCode Coupon code to delete
     */
    function deleteDiscountCoupon(bytes32 couponCode) external onlyOwner {
        require(discountCoupons[couponCode].discountPercentage > 0, "Coupon does not exist");
        
        delete discountCoupons[couponCode];
        
        emit CouponDeleted(couponCode);
    }
    
    // ============= Purchase Functions =============
    /**
     * @dev Purchase a core plan subscription
     * @param planType Core plan type
     */
    function purchaseCorePlan(CorePlanType planType) 
        external 
        payable 
        whenNotPaused 
    {
        uint8 planTypeId = uint8(planType);
        Plan storage plan = corePlans[planTypeId];
        
        require(plan.isActive, "Plan is not active");
        require(msg.value >= plan.price, "Insufficient payment amount");
        
        _processSubscriptionPurchase(
            planTypeId,
            plan.price,
            plan.duration,
            address(0),
            bytes32(0)
        );
        
        if (msg.value > plan.price) {
            payable(msg.sender).transfer(msg.value - plan.price);
        }
    }
    
    /**
     * @dev Purchase a core plan subscription with referral
     * @param planType Core plan type
     * @param referrer Referrer address
     */
    function purchaseCorePlanWithReferral(CorePlanType planType, address referrer) 
        external 
        payable 
        whenNotPaused 
    {
        require(referralSystemAddress != address(0), "ReferralSystem not set");
        require(referrer != msg.sender, "Cannot refer yourself");
        require(referrer != address(0), "Invalid referrer address");
        
        uint8 planTypeId = uint8(planType);
        Plan storage plan = corePlans[planTypeId];
        
        require(plan.isActive, "Plan is not active");
        
        // Get discount from ReferralSystem contract
        uint8 discountPercentage = IReferralSystem(referralSystemAddress).getReferralDiscount(referrer);
        uint256 discountedPrice = _calculateDiscountedPrice(plan.price, discountPercentage);
        
        require(msg.value >= discountedPrice, "Insufficient payment amount");
        
        uint256 purchaseId = nextPurchaseId;
        _processSubscriptionPurchase(
            planTypeId,
            discountedPrice,
            plan.duration,
            referrer,
            bytes32(0)
        );
        
        // Call process reward in ReferralSystem
        IReferralSystem(referralSystemAddress).processReferralReward(
            referrer, 
            msg.sender, 
            planTypeId, 
            purchaseId,
            plan.price // Original price without discount
        );
        
        if (msg.value > discountedPrice) {
            payable(msg.sender).transfer(msg.value - discountedPrice);
        }
    }
    
    /**
     * @dev Purchase a core plan subscription with discount coupon
     * @param planType Core plan type
     * @param couponCode Discount coupon code
     */
    function purchaseCorePlanWithCoupon(CorePlanType planType, bytes32 couponCode) 
        external 
        payable 
        whenNotPaused 
    {
        uint8 planTypeId = uint8(planType);
        Plan storage plan = corePlans[planTypeId];
        
        require(plan.isActive, "Plan is not active");
        
        (bool isValid, uint8 discountPercentage) = _validateCoupon(couponCode, planTypeId);
        require(isValid, "Invalid or expired coupon");
        
        uint256 discountedPrice = _calculateDiscountedPrice(plan.price, discountPercentage);
        
        require(msg.value >= discountedPrice, "Insufficient payment amount");
        
        _processSubscriptionPurchase(
            planTypeId,
            discountedPrice,
            plan.duration,
            address(0),
            couponCode
        );
        
        _updateCouponUsage(couponCode);
        
        if (msg.value > discountedPrice) {
            payable(msg.sender).transfer(msg.value - discountedPrice);
        }
    }
    
    // ============= Internal Functions =============
    /**
     * @dev Process a subscription purchase
     * @param planId Plan ID (core plan type)
     * @param amountPaid Amount paid for the subscription
     * @param duration Subscription duration in seconds
     * @param referrer Referrer address (if any)
     * @param couponCode Coupon code used (if any)
     */
    function _processSubscriptionPurchase(
        uint256 planId,
        uint256 amountPaid,
        uint256 duration,
        address referrer,
        bytes32 couponCode
    ) 
        internal 
    {
        uint256 purchaseId = nextPurchaseId++;
        
        uint256 expiryTime;
        if (userSubscriptions[msg.sender].expiryTime > block.timestamp) {
            expiryTime = userSubscriptions[msg.sender].expiryTime + duration;
        } else {
            expiryTime = block.timestamp + duration;
        }
        
        UserSubscription storage subscription = userSubscriptions[msg.sender];
        subscription.expiryTime = expiryTime;
        subscription.purchaseIds.push(purchaseId);
        
        purchases[purchaseId] = Purchase({
            user: msg.sender,
            planId: planId,
            pricePaid: amountPaid,
            purchaseTime: block.timestamp,
            referrer: referrer,
            couponCode: couponCode,
            subscriptionEnd: expiryTime
        });
        
        emit SubscriptionPurchased(
            msg.sender,
            planId,
            purchaseId,
            expiryTime,
            amountPaid
        );
    }
    
    /**
     * @dev Validate a coupon
     * @param couponCode Coupon code
     * @param planId Plan ID
     * @return isValid Whether the coupon is valid
     * @return discountPercentage Discount percentage of the coupon
     */
    function _validateCoupon(
        bytes32 couponCode,
        uint256 planId
    ) 
        internal 
        view 
        returns (bool isValid, uint8 discountPercentage) 
    {
        DiscountCoupon storage coupon = discountCoupons[couponCode];
        
        if (coupon.discountPercentage == 0) {
            return (false, 0);
        }
        
        if (!coupon.isActive) {
            return (false, 0);
        }
        
        if (coupon.usageLimit > 0 && coupon.usageCount >= coupon.usageLimit) {
            return (false, 0);
        }
        
        if (block.timestamp < coupon.startTime) {
            return (false, 0);
        }
        
        if (coupon.endTime > 0 && block.timestamp > coupon.endTime) {
            return (false, 0);
        }
        
        if (coupon.isPlanRestricted) {
            // Check plan restrictions for core plans
            if (!couponPlanRestrictions[couponCode][planId]) {
                return (false, 0);
            }
            
            // Validate that plan exists
            if (planId >= 4 || bytes(corePlans[uint8(planId)].name).length == 0) {
                return (false, 0);
            }
        }
        
        if (coupon.isUserRestricted && !couponUserRestrictions[couponCode][msg.sender]) {
            return (false, 0);
        }
        
        return (true, coupon.discountPercentage);
    }
    
    /**
     * @dev Update coupon usage count
     * @param couponCode Coupon code
     */
    function _updateCouponUsage(bytes32 couponCode) internal {
        DiscountCoupon storage coupon = discountCoupons[couponCode];
        coupon.usageCount++;
        
        emit DiscountCouponUsed(
            msg.sender,
            couponCode,
            coupon.discountPercentage,
            nextPurchaseId - 1
        );
    }
    
    /**
     * @dev Calculate discounted price
     * @param originalPrice Original price
     * @param discountPercentage Discount percentage
     * @return Discounted price
     */
    function _calculateDiscountedPrice(
        uint256 originalPrice,
        uint8 discountPercentage
    ) 
        internal 
        pure 
        returns (uint256) 
    {
        return (originalPrice * (100 - discountPercentage)) / 100;
    }
    
    // ============= Query Functions =============
    /**
     * @dev Get plan details
     * @param planId Plan ID
     * @return name Plan name
     * @return price Plan price
     * @return duration Plan duration
     * @return isActive Whether the plan is active
     */
    function getPlanDetails(uint256 planId)
        external
        view
        returns (
            string memory name,
            uint256 price,
            uint256 duration,
            bool isActive
        )
    {
        require(planId < 4, "Invalid plan ID");
        Plan storage plan = corePlans[uint8(planId)];
        return (plan.name, plan.price, plan.duration, plan.isActive);
    }
    
    /**
     * @dev Get purchase details
     * @param purchaseId Purchase ID
     * @return user User address
     * @return planId Plan ID
     * @return pricePaid Price paid
     * @return purchaseTime Purchase time
     * @return referrer Referrer address
     * @return couponCode Coupon code used
     * @return subscriptionEnd Subscription end time
     */
    function getPurchaseDetails(uint256 purchaseId)
        external
        view
        returns (
            address user,
            uint256 planId,
            uint256 pricePaid,
            uint256 purchaseTime,
            address referrer,
            bytes32 couponCode,
            uint256 subscriptionEnd
        )
    {
        require(purchaseId > 0 && purchaseId < nextPurchaseId, "Invalid purchase ID");
        
        Purchase storage purchase = purchases[purchaseId];
        return (
            purchase.user,
            purchase.planId,
            purchase.pricePaid,
            purchase.purchaseTime,
            purchase.referrer,
            purchase.couponCode,
            purchase.subscriptionEnd
        );
    }
    
    /**
     * @dev Get user's purchase history
     * @param user User address
     * @return purchaseIds Array of purchase IDs
     */
    function getUserPurchases(address user)
        external
        view
        returns (uint256[] memory purchaseIds)
    {
        return userSubscriptions[user].purchaseIds;
    }
    
    /**
     * @dev Check if a user has valid subscription
     * @param user User address
     * @return Whether the user has a valid subscription
     */
    function hasSubscription(address user) external view returns (bool) {
        return userSubscriptions[user].expiryTime > block.timestamp;
    }
    
    /**
     * @dev Get subscription expiry time
     * @param user User address
     * @return Expiry time
     */
    function getSubscriptionExpiry(address user) external view returns (uint256) {
        return userSubscriptions[user].expiryTime;
    }
    
    /**
     * @dev Get remaining subscription time in seconds
     * @param user User address
     * @return Remaining time in seconds
     */
    function getRemainingTime(address user) external view returns (uint256) {
        uint256 expiryTime = userSubscriptions[user].expiryTime;
        if (expiryTime <= block.timestamp) {
            return 0;
        }
        return expiryTime - block.timestamp;
    }
    
    /**
     * @dev Receive function to accept ETH payments
     */
    receive() external payable {}
}

Tags:
addr:0x7ebfb69ef7b3ce42c3cabcadee9b6c8620f748da|verified:true|block:23668335|tx:0x35a2f933159827411d8a028a5e25a35aff77a3f1f29f74020d28424a72b14606|first_check:1761567306

Submitted on: 2025-10-27 13:15:07

Comments

Log in to comment.

No comments yet.