feat(admin): Paddle client behind an interface, config, and the event idempotency record

Client is a thin REST client (net/http) rather than the vendor SDK: the surface
we need is two calls, and a hand-rolled client has no version-drift risk and no
dependency in go.sum. All Paddle wire shapes live only in http.go.

PADDLE_API_KEY and PADDLE_WEBHOOK_SECRET are boot-required — an unverified
webhook endpoint is one anyone can issue licences through. paddle_events carries
a unique index on event_id for webhook idempotency.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-27 10:36:26 +01:00
co-authored by Claude Opus 5
parent 3d3be7465f
commit fbd93d0ea5
8 changed files with 323 additions and 1 deletions
+5
View File
@@ -21,6 +21,7 @@ import (
"github.com/mrhid6/vantage/admin/internal/lifecycle"
"github.com/mrhid6/vantage/admin/internal/mail"
"github.com/mrhid6/vantage/admin/internal/models"
"github.com/mrhid6/vantage/admin/internal/paddle"
)
func main() {
@@ -34,6 +35,10 @@ func main() {
licensing.SetSigningKey(cfg.SigningKey)
api.SetAppLoginURL(cfg.AppLoginURL)
if _, err := paddle.Init(cfg.PaddleAPIKey, cfg.PaddleEnv); err != nil {
log.Fatalf("paddle init: %v", err)
}
mail.Init(mail.Config{
Host: cfg.SMTPHost, Port: cfg.SMTPPort, From: cfg.SMTPFrom,
Username: cfg.SMTPUsername, Password: cfg.SMTPPassword,