AtlatestRepositorysigil-lemonsqueezy
sigil-lemonsqueezy / tree / src / lemonsqueezylicense.sgl
1
;;; (lemonsqueezy license) - License key management.2
;;;3
;;; Two separate APIs:4
;;; 1. Main API (requires Bearer auth) — list/retrieve/update license keys5
;;; 2. License API (no auth needed) — validate/activate/deactivate keys6
;;;7
;;; The License API is designed for client applications and uses different8
;;; headers (application/json, application/x-www-form-urlencoded).10
(define-library (lemonsqueezy license)11
(import (sigil core)12
(sigil dict)13
(sigil string)14
(sigil struct)15
(sigil json)16
(only (sigil http) url-encode-value)17
(sigil http client)18
(lemonsqueezy))20
(export ;; Records21
ls-license-key22
ls-license-key?23
ls-license-key-id24
ls-license-key-store-id25
ls-license-key-customer-id26
ls-license-key-order-id27
ls-license-key-product-id28
ls-license-key-key29
ls-license-key-key-short30
ls-license-key-activation-limit31
ls-license-key-instances-count32
ls-license-key-disabled33
ls-license-key-status34
ls-license-key-expires-at35
ls-license-key-created-at36
ls-license-key-updated-at38
ls-license-validation39
ls-license-validation?40
ls-license-validation-valid41
ls-license-validation-error42
ls-license-validation-license-key43
ls-license-validation-instance44
ls-license-validation-meta46
ls-license-activation47
ls-license-activation?48
ls-license-activation-activated49
ls-license-activation-error50
ls-license-activation-license-key51
ls-license-activation-instance52
ls-license-activation-meta54
;; Parsing55
parse-license-key56
parse-license-validation57
parse-license-activation59
;; Main API functions (requires auth)60
ls-license-keys61
ls-license-key-get62
ls-license-key-update64
;; License API functions (no auth required)65
ls-validate-license66
ls-activate-license67
ls-deactivate-license)69
(begin71
;; ---------------------------------------------------------------72
;; Records73
;; ---------------------------------------------------------------75
(define-struct ls-license-key76
(id)77
(store-id default: #f)78
(customer-id default: #f)79
(order-id default: #f)80
(product-id default: #f)81
(key default: "")82
(key-short default: "")83
(activation-limit default: 0)84
(instances-count default: 0)85
(disabled default: #f)86
(status default: "inactive")87
(expires-at default: #f)88
(created-at default: #f)89
(updated-at default: #f))91
(define-struct ls-license-validation92
(valid default: #f)93
(error default: #f)94
(license-key default: #{})95
(instance default: #f)96
(meta default: #{}))98
(define-struct ls-license-activation99
(activated default: #f)100
(error default: #f)101
(license-key default: #{})102
(instance default: #f)103
(meta default: #{}))105
;; ---------------------------------------------------------------106
;; Parsing107
;; ---------------------------------------------------------------109
;;; Parse a JSON:API license key resource into an ls-license-key record.110
(define (parse-license-key resource)111
(ls-license-key112
id: (jsonapi-id resource)113
store-id: (jsonapi-attr resource store_id: #f)114
customer-id: (jsonapi-attr resource customer_id: #f)115
order-id: (jsonapi-attr resource order_id: #f)116
product-id: (jsonapi-attr resource product_id: #f)117
key: (jsonapi-attr resource key: "")118
key-short: (jsonapi-attr resource key_short: "")119
activation-limit: (jsonapi-attr resource activation_limit: 0)120
instances-count: (jsonapi-attr resource instances_count: 0)121
disabled: (jsonapi-attr resource disabled: #f)122
status: (jsonapi-attr resource status: "inactive")123
expires-at: (jsonapi-attr resource expires_at: #f)124
created-at: (jsonapi-attr resource created_at: #f)125
updated-at: (jsonapi-attr resource updated_at: #f)))127
;;; Normalize a dict field: null → empty dict, missing → empty dict.128
(define (dict-or-empty data key)129
(let ((v (dict-ref data key #f)))130
(if (or (not v) (eq? v 'null)) #{} v)))132
;;; Parse a License API validation response.133
(define (parse-license-validation data)134
(ls-license-validation135
valid: (dict-ref data valid: #f)136
error: (maybe-null (dict-ref data error: #f))137
license-key: (dict-or-empty data license_key:)138
instance: (maybe-null (dict-ref data instance: #f))139
meta: (dict-or-empty data meta:)))141
;;; Parse a License API activation/deactivation response.142
(define (parse-license-activation data)143
(ls-license-activation144
activated: (dict-ref data activated: #f)145
error: (maybe-null (dict-ref data error: #f))146
license-key: (dict-or-empty data license_key:)147
instance: (maybe-null (dict-ref data instance: #f))148
meta: (dict-or-empty data meta:)))150
;; ---------------------------------------------------------------151
;; License API helpers (no Bearer auth)152
;; ---------------------------------------------------------------154
;;; The License API uses different headers than the main API.155
(define license-api-headers156
#{ accept: "application/json"157
content-type: "application/x-www-form-urlencoded" })159
;;; License API base URL (hardcoded; does not use client base-url160
;;; since these endpoints require no authentication).161
(define license-api-base "https://api.lemonsqueezy.com")163
;;; Build a form-urlencoded body from an alist of (key . value) pairs.164
(define (form-encode-body params)165
(string-join166
(map (lambda (p)167
(string-append (url-encode-value (car p))168
"="169
(url-encode-value (cdr p))))170
(filter (lambda (p) (cdr p)) params))171
"&"))173
;;; Make a POST request to the License API (form-urlencoded, no auth).174
(define (license-post url params)175
(let* ((body (form-encode-body params))176
(response (http-post url body headers: license-api-headers)))177
(if (not (http-response? response))178
(error "License API request failed: no response"))179
(let ((status (http-response-status response))180
(resp-body (http-response-body response)))181
(if (>= status 500)182
(error (string-append "License API server error "183
(number->string status))))184
(if (and resp-body (not (string=? resp-body "")))185
(json-decode resp-body)186
#{}))))188
;; ---------------------------------------------------------------189
;; Main API functions (requires auth)190
;; ---------------------------------------------------------------192
;;; List license keys with optional filtering and pagination.193
(define (ls-license-keys client . rest)194
(apply ls-list-endpoint client parse-license-key '("v1" "license-keys") rest))196
;;; Get a single license key by ID (main API).197
(define (ls-license-key-get client license-key-id)198
(ls-get-endpoint client parse-license-key '("v1" "license-keys") license-key-id))200
;;; Update a license key.201
;;; updates is a dict of attributes (e.g., activation_limit:, disabled:, expires_at:).202
;;; Returns an ls-license-key record.203
(define (ls-license-key-update client license-key-id updates)204
(let* ((body #{ data:205
#{ type: "license-keys"206
id: license-key-id207
attributes: updates } })208
(url (ls-api-url client "v1" "license-keys" license-key-id))209
(response (ls-patch/json client url body)))210
(parse-license-key (jsonapi-data response))))212
;; ---------------------------------------------------------------213
;; License API functions (no auth required)214
;; ---------------------------------------------------------------216
;;; Validate a license key.217
;;; license-key: the full license key string218
;;; instance-id: optional instance UUID to validate a specific activation219
;;;220
;;; Returns an ls-license-validation record.221
;;; Rate limit: 60 requests/minute.222
(define (ls-validate-license license-key . rest)223
(let* ((instance-id (if (null? rest) #f (car rest)))224
(params (list (cons "license_key" license-key)225
(cons "instance_id" instance-id)))226
(url (string-append license-api-base "/v1/licenses/validate"))227
(data (license-post url params)))228
(parse-license-validation data)))230
;;; Activate a license key.231
;;; license-key: the full license key string232
;;; instance-name: label for this activation (e.g., "example.com")233
;;;234
;;; Returns an ls-license-activation record.235
;;; Rate limit: 60 requests/minute.236
(define (ls-activate-license license-key instance-name)237
(let* ((params (list (cons "license_key" license-key)238
(cons "instance_name" instance-name)))239
(url (string-append license-api-base "/v1/licenses/activate"))240
(data (license-post url params)))241
(parse-license-activation data)))243
;;; Deactivate a license key.244
;;; license-key: the full license key string245
;;; instance-id: the UUID of the instance to deactivate246
;;;247
;;; Returns an ls-license-activation record (with deactivated: #t).248
;;; Rate limit: 60 requests/minute.249
(define (ls-deactivate-license license-key instance-id)250
(let* ((params (list (cons "license_key" license-key)251
(cons "instance_id" instance-id)))252
(url (string-append license-api-base "/v1/licenses/deactivate"))253
(data (license-post url params)))254
(parse-license-activation data)))256
))