Source Code

;; Subscription Manager - Clarity 4
;; Recurring subscription payments

(define-constant contract-owner tx-sender)
(define-constant err-not-active (err u7000))
(define-constant err-payment-failed (err u7001))

(define-data-var subscription-nonce uint u0)

(define-map subscriptions
    uint
    {
        subscriber: principal,
        provider: principal,
        amount-per-period: uint,
        period-length: uint,
        start-height: uint,
        last-payment: uint,
        active: bool,
        total-paid: uint
    }
)

(define-map subscriber-subs principal (list 20 uint))

(define-read-only (get-subscription (sub-id uint))
    (map-get? subscriptions sub-id)
)

(define-read-only (is-payment-due (sub-id uint))
    (match (get-subscription sub-id)
        sub
        (let (
            (next-payment (+ (get last-payment sub) (get period-length sub)))
        )
            (and (get active sub) (>= stacks-block-height next-payment))
        )
        false
    )
)

(define-public (create-subscription
    (provider principal)
    (amount uint)
    (period-blocks uint)
)
    (let (
        (sub-id (var-get subscription-nonce))
        (user-subs (default-to (list) (map-get? subscriber-subs tx-sender)))
    )
        (map-set subscriptions sub-id {
            subscriber: tx-sender,
            provider: provider,
            amount-per-period: amount,
            period-length: period-blocks,
            start-height: stacks-block-height,
            last-payment: stacks-block-height,
            active: true,
            total-paid: u0
        })
        
        (map-set subscriber-subs tx-sender
            (unwrap-panic (as-max-len? (append user-subs sub-id) u20))
        )
        (var-set subscription-nonce (+ sub-id u1))
        (ok sub-id)
    )
)

(define-public (process-payment (sub-id uint))
    (let (
        (sub (unwrap! (get-subscription sub-id) err-not-active))
    )
        (asserts! (is-payment-due sub-id) err-not-active)
        
        (try! (stx-transfer? (get amount-per-period sub) 
            (get subscriber sub) (get provider sub)))
        
        (ok (map-set subscriptions sub-id 
            (merge sub {
                last-payment: stacks-block-height,
                total-paid: (+ (get total-paid sub) (get amount-per-period sub))
            })
        ))
    )
)

(define-public (cancel-subscription (sub-id uint))
    (let (
        (sub (unwrap! (get-subscription sub-id) err-not-active))
    )
        (asserts! (is-eq tx-sender (get subscriber sub)) err-not-active)
        (ok (map-set subscriptions sub-id (merge sub {active: false})))
    )
)

Functions (4)

FunctionAccessArgs
get-subscriptionread-onlysub-id: uint
is-payment-dueread-onlysub-id: uint
process-paymentpublicsub-id: uint
cancel-subscriptionpublicsub-id: uint