Source Code

(define-constant contract-owner tx-sender)
(define-constant err-owner-only (err u100))
(define-constant err-not-found (err u101))
(define-constant err-unauthorized (err u102))
(define-constant err-invalid-amount (err u104))
(define-constant err-invalid-product (err u118))

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

(define-map finance-products
  uint
  {
    provider: principal,
    borrower: principal,
    product-type: (string-ascii 30),
    amount: uint,
    term-rate: uint,
    specific-parameters: (string-ascii 100),
    outstanding: uint,
    active: bool,
    issue-block: uint,
    maturity-block: uint
  }
)

(define-map product-payments
  {product-id: uint, payment-id: uint}
  {
    amount: uint,
    block: uint,
    fee-portion: uint
  }
)

(define-map payment-counter uint uint)
(define-map provider-products principal (list 50 uint))
(define-map borrower-products principal (list 30 uint))

(define-public (create-product (borrower principal) (product-type (string-ascii 30)) (amount uint) 
                                (rate uint) (parameters (string-ascii 100)) (maturity uint))
  (let
    (
      (product-id (+ (var-get product-nonce) u1))
    )
    (asserts! (> amount u0) err-invalid-amount)
    (map-set finance-products product-id {
      provider: tx-sender,
      borrower: borrower,
      product-type: product-type,
      amount: amount,
      term-rate: rate,
      specific-parameters: parameters,
      outstanding: amount,
      active: true,
      issue-block: stacks-block-height,
      maturity-block: maturity
    })
    (map-set payment-counter product-id u0)
    (map-set provider-products tx-sender
      (unwrap-panic (as-max-len? (append (default-to (list) (map-get? provider-products tx-sender)) product-id) u50)))
    (map-set borrower-products borrower
      (unwrap-panic (as-max-len? (append (default-to (list) (map-get? borrower-products borrower)) product-id) u30)))
    (var-set product-nonce product-id)
    (ok product-id)
  )
)

(define-public (disburse-product (product-id uint))
  (let
    (
      (product (unwrap! (map-get? finance-products product-id) err-not-found))
    )
    (asserts! (is-eq tx-sender (get provider product)) err-unauthorized)
    (asserts! (get active product) err-invalid-product)
    (try! (stx-transfer? (get amount product) tx-sender (get borrower product)))
    (ok true)
  )
)

(define-public (make-payment (product-id uint) (amount uint))
  (let
    (
      (product (unwrap! (map-get? finance-products product-id) err-not-found))
      (fees (calculate-fees product-id))
      (new-outstanding (if (>= amount (get outstanding product)) 
                          u0 
                          (- (get outstanding product) amount)))
      (pid (+ (default-to u0 (map-get? payment-counter product-id)) u1))
    )
    (asserts! (is-eq tx-sender (get borrower product)) err-unauthorized)
    (asserts! (get active product) err-invalid-product)
    (try! (stx-transfer? amount tx-sender (get provider product)))
    (map-set product-payments {product-id: product-id, payment-id: pid} {
      amount: amount,
      block: stacks-block-height,
      fee-portion: fees
    })
    (map-set payment-counter product-id pid)
    (map-set finance-products product-id (merge product {
      outstanding: new-outstanding,
      active: (> new-outstanding u0)
    }))
    (ok true)
  )
)

(define-public (restructure-product (product-id uint) (new-rate uint) (new-maturity uint))
  (let
    (
      (product (unwrap! (map-get? finance-products product-id) err-not-found))
    )
    (asserts! (is-eq tx-sender (get provider product)) err-unauthorized)
    (map-set finance-products product-id (merge product {
      term-rate: new-rate,
      maturity-block: new-maturity
    }))
    (ok true)
  )
)

(define-public (settle-product (product-id uint))
  (let
    (
      (product (unwrap! (map-get? finance-products product-id) err-not-found))
      (total-due (calculate-total-due product-id))
    )
    (asserts! (is-eq tx-sender (get borrower product)) err-unauthorized)
    (asserts! (get active product) err-invalid-product)
    (try! (stx-transfer? total-due tx-sender (get provider product)))
    (map-set finance-products product-id (merge product {outstanding: u0, active: false}))
    (ok true)
  )
)

(define-read-only (get-product (product-id uint))
  (ok (map-get? finance-products product-id))
)

(define-read-only (get-payment (product-id uint) (payment-id uint))
  (ok (map-get? product-payments {product-id: product-id, payment-id: payment-id}))
)

(define-read-only (get-borrower-products (borrower principal))
  (ok (map-get? borrower-products borrower))
)

(define-read-only (calculate-fees (product-id uint))
  (let
    (
      (product (unwrap-panic (map-get? finance-products product-id)))
      (outstanding (get outstanding product))
      (rate (get term-rate product))
      (elapsed (- stacks-block-height (get issue-block product)))
    )
    (/ (* outstanding (* rate elapsed)) u10000000)
  )
)

(define-read-only (calculate-total-due (product-id uint))
  (let
    (
      (product (unwrap-panic (map-get? finance-products product-id)))
      (outstanding (get outstanding product))
      (fees (calculate-fees product-id))
    )
    (+ outstanding fees)
  )
)

(define-read-only (get-product-status (product-id uint))
  (let
    (
      (product (unwrap-panic (map-get? finance-products product-id)))
    )
    (ok {
      active: (get active product),
      outstanding: (get outstanding product),
      overdue: (> stacks-block-height (get maturity-block product)),
      product-type: (get product-type product)
    })
  )
)

Functions (11)

FunctionAccessArgs
create-productpublicborrower: principal, product-type: (string-ascii 30
disburse-productpublicproduct-id: uint
make-paymentpublicproduct-id: uint, amount: uint
restructure-productpublicproduct-id: uint, new-rate: uint, new-maturity: uint
settle-productpublicproduct-id: uint
get-productread-onlyproduct-id: uint
get-paymentread-onlyproduct-id: uint, payment-id: uint
get-borrower-productsread-onlyborrower: principal
calculate-feesread-onlyproduct-id: uint
calculate-total-dueread-onlyproduct-id: uint
get-product-statusread-onlyproduct-id: uint