Source Code

;; Stacks Subscription - Recurring Payments
;; On-chain subscription management

(define-constant contract-owner tx-sender)
(define-constant treasury 'SP2PEBKJ2W1ZDDF2QQ6Y4FXKZEDPT9J9R2NKD9WJB)
(define-constant err-not-found (err u100))
(define-constant err-already-subscribed (err u101))
(define-constant err-not-subscribed (err u102))
(define-constant err-expired (err u103))

;; Subscription tiers
(define-constant TIER-BASIC u1)
(define-constant TIER-PRO u2)
(define-constant TIER-ENTERPRISE u3)

;; Prices per month (in blocks ~4320)
(define-constant PRICE-BASIC u5000000)      ;; 5 STX
(define-constant PRICE-PRO u15000000)       ;; 15 STX
(define-constant PRICE-ENTERPRISE u50000000) ;; 50 STX
(define-constant MONTH-BLOCKS u4320)

(define-data-var total-subscribers uint u0)
(define-data-var total-revenue uint u0)

(define-map subscriptions principal
  {
    tier: uint,
    start-block: uint,
    end-block: uint,
    auto-renew: bool,
    total-paid: uint
  }
)

(define-map tier-stats uint
  {
    subscribers: uint,
    revenue: uint
  }
)

(define-read-only (get-subscription (user principal))
  (map-get? subscriptions user)
)

(define-read-only (is-active (user principal))
  (match (map-get? subscriptions user)
    sub (<= stacks-block-height (get end-block sub))
    false
  )
)

(define-read-only (get-tier (user principal))
  (match (map-get? subscriptions user)
    sub (if (is-active user) (get tier sub) u0)
    u0
  )
)

(define-read-only (get-tier-price (tier uint))
  (if (is-eq tier TIER-BASIC) PRICE-BASIC
    (if (is-eq tier TIER-PRO) PRICE-PRO
      (if (is-eq tier TIER-ENTERPRISE) PRICE-ENTERPRISE
        u0
      )
    )
  )
)

(define-read-only (get-stats)
  {
    total-subscribers: (var-get total-subscribers),
    total-revenue: (var-get total-revenue)
  }
)

(define-public (subscribe (tier uint) (months uint))
  (let (
    (price (* (get-tier-price tier) months))
    (duration (* MONTH-BLOCKS months))
    (existing (map-get? subscriptions tx-sender))
  )
    (asserts! (> price u0) err-not-found)
    
    (match existing
      sub
      ;; Extend existing subscription
      (map-set subscriptions tx-sender
        (merge sub {
          tier: tier,
          end-block: (+ (get end-block sub) duration),
          total-paid: (+ (get total-paid sub) price)
        })
      )
      ;; New subscription
      (begin
        (map-set subscriptions tx-sender {
          tier: tier,
          start-block: stacks-block-height,
          end-block: (+ stacks-block-height duration),
          auto-renew: false,
          total-paid: price
        })
        (var-set total-subscribers (+ (var-get total-subscribers) u1))
      )
    )
    
    (var-set total-revenue (+ (var-get total-revenue) price))
    
    (ok { tier: tier, months: months, end-block: (+ stacks-block-height duration) })
  )
)

(define-public (set-auto-renew (enabled bool))
  (match (map-get? subscriptions tx-sender)
    sub
    (begin
      (map-set subscriptions tx-sender (merge sub { auto-renew: enabled }))
      (ok true)
    )
    err-not-subscribed
  )
)

(define-public (cancel-subscription)
  (match (map-get? subscriptions tx-sender)
    sub
    (begin
      (map-set subscriptions tx-sender (merge sub { auto-renew: false }))
      (ok true)
    )
    err-not-subscribed
  )
)

Functions (8)

FunctionAccessArgs
get-subscriptionread-onlyuser: principal
is-activeread-onlyuser: principal
get-tierread-onlyuser: principal
get-tier-priceread-onlytier: uint
get-statsread-only
subscribepublictier: uint, months: uint
set-auto-renewpublicenabled: bool
cancel-subscriptionpublic