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 {}
}
Submitted on: 2025-10-27 13:15:07
Comments
Log in to comment.
No comments yet.