package handlers import ( "encoding/json" "errors" "net/http" "strings" "time" "github.com/go-chi/chi/v5" "github.com/noderunners/nip05api/internal/invoice" "github.com/noderunners/nip05api/internal/nostr" "github.com/noderunners/nip05api/internal/user" ) type Invoices struct { Service *invoice.Service LightningEnabled bool } type createInvoiceReq struct { Username string `json:"username"` Pubkey string `json:"pubkey"` SubscriptionType string `json:"subscription_type"` Years int `json:"years"` } func (h *Invoices) Create(w http.ResponseWriter, r *http.Request) { if !h.LightningEnabled { WriteError(w, http.StatusServiceUnavailable, "LightningDisabled", "lightning payments are disabled") return } var body createInvoiceReq if err := json.NewDecoder(r.Body).Decode(&body); err != nil { WriteError(w, http.StatusBadRequest, "ValidationError", "invalid JSON") return } hexpk, err := nostr.NormalizePubkey(body.Pubkey) if err != nil { WriteError(w, http.StatusBadRequest, "ValidationError", "Invalid pubkey format") return } subStr := strings.TrimSpace(body.SubscriptionType) if subStr == "" { subStr = string(user.SubLifetime) } sub := user.SubscriptionType(subStr) if !sub.Valid() { WriteError(w, http.StatusBadRequest, "ValidationError", "invalid subscription_type") return } years := body.Years if sub == user.SubYearly && years <= 0 { years = 1 } p, err := h.Service.Create(r.Context(), invoice.CreateRequest{ Username: body.Username, Pubkey: hexpk, SubscriptionType: sub, Years: years, }) if err != nil { switch { case errors.Is(err, invoice.ErrLifetimeAccess): WriteError(w, http.StatusForbidden, "User already has lifetime access", "") case errors.Is(err, invoice.ErrUsernameTaken), errors.Is(err, user.ErrUsernameTaken): WriteError(w, http.StatusConflict, "Conflict", "username unavailable") case errors.Is(err, invoice.ErrUsernameMismatch): WriteError(w, http.StatusConflict, "Conflict", err.Error()) case errors.Is(err, user.ErrInvalidUsername), errors.Is(err, invoice.ErrInvalidYears): WriteError(w, http.StatusBadRequest, "ValidationError", err.Error()) case errors.Is(err, invoice.ErrLNbits): WriteError(w, http.StatusServiceUnavailable, "LightningError", err.Error()) default: WriteError(w, http.StatusInternalServerError, "InternalError", err.Error()) } return } WriteJSON(w, http.StatusOK, map[string]any{ "payment_hash": p.PaymentHash, "payment_request": p.PaymentRequest, "amount_sats": p.AmountSats, "expires_at": p.ExpiresAt.UTC().Format(time.RFC3339), "username": p.Username, "is_renewal": p.IsRenewal, }) } func (h *Invoices) Get(w http.ResponseWriter, r *http.Request) { hash := chi.URLParam(r, "payment_hash") p, err := h.Service.Repo().Get(r.Context(), hash) if errors.Is(err, invoice.ErrInvoiceNotFound) { WriteError(w, http.StatusNotFound, "NotFound", "invoice not found") return } if err != nil { WriteError(w, http.StatusInternalServerError, "InternalError", err.Error()) return } WriteJSON(w, http.StatusOK, map[string]any{ "payment_hash": p.PaymentHash, "status": string(p.Status()), "username": p.Username, }) }