Files
Nip-05-api/internal/http/handlers/invoices.go

108 lines
3.2 KiB
Go

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,
})
}