Implement order placed webhook

This commit is contained in:
Luca 2022-07-24 21:41:01 +02:00
parent 36d98f365a
commit 03e1e564f6
4 changed files with 149 additions and 19 deletions

View File

@ -26,6 +26,8 @@ type MatrixConfig struct {
} }
type ServerConfig struct { type ServerConfig struct {
APIToken string `json:"api_token"`
BaseURL string `json:"base_url"`
ListenAddress string `json:"listen_address"` ListenAddress string `json:"listen_address"`
} }

View File

@ -0,0 +1,98 @@
package pretix
import (
"encoding/json"
"fmt"
"log"
"net/http"
"time"
)
const (
eventOrderPlaced = "pretix.event.order.placed"
urlIndividualOrder = "%s/api/v1/organizers/%s/events/%s/orders/%s/"
)
func (s Server) orderPlaced(w http.ResponseWriter, r *http.Request) {
if r.Header.Get("Content-Type") != "application/json" {
writeStatus(w, http.StatusBadRequest)
return
}
data := struct{
Action string
Code string
Event string
Organizer string
}{}
decoder := json.NewDecoder(r.Body)
err := decoder.Decode(&data)
if err != nil {
writeStatus(w, http.StatusBadRequest)
return
}
if data.Action != eventOrderPlaced {
writeStatus(w, http.StatusBadRequest)
return
}
req, err := http.NewRequest(http.MethodGet, s.buildURL(urlIndividualOrder, data.Organizer, data.Event, data.Code), nil)
if err != nil {
writeStatus(w, http.StatusInternalServerError)
return
}
req.Header.Set("Accept", "application/json")
req.Header.Set("Authorization", fmt.Sprint("Token ", s.config.APIToken))
resp, err := s.client.Do(req)
if err != nil {
writeStatus(w, http.StatusInternalServerError)
return
}
defer resp.Body.Close()
if resp.StatusCode != 200 {
writeStatus(w, http.StatusInternalServerError)
return
}
if resp.Header.Get("Content-Type") != "application/json" {
writeStatus(w, http.StatusInternalServerError)
return
}
decoder = json.NewDecoder(resp.Body)
order := struct{
Code string
Datetime time.Time
Positions []struct{
Price string
}
}{}
err = decoder.Decode(&order)
if err != nil {
writeStatus(w, http.StatusInternalServerError)
return
}
result, err := s.db.Exec("INSERT INTO orders VALUES(?);", order.Code)
if err != nil {
writeStatus(w, http.StatusInternalServerError)
return
}
rowsAffected, err := result.RowsAffected()
if err != nil {
writeStatus(w, http.StatusInternalServerError)
return
}
if rowsAffected > 0 && order.Datetime.Add(72 * time.Hour).After(time.Now()) { // pretix retries failed webhooks for up to three days
log.Print(order)
}
}

View File

@ -2,10 +2,12 @@ package pretix
import ( import (
"context" "context"
"encoding/json" "database/sql"
"fmt"
"io" "io"
"log" "log"
"net/http" "net/http"
"strings"
"sync" "sync"
"git.luj0ga.de/franconian/matrix-pretix/internal/config" "git.luj0ga.de/franconian/matrix-pretix/internal/config"
@ -14,21 +16,30 @@ import (
type Server struct { type Server struct {
client http.Client client http.Client
config *config.ServerConfig
db *sql.DB
matrix *matrix.Client matrix *matrix.Client
} }
func NewServer(matrix *matrix.Client) *Server { func NewServer(config *config.ServerConfig, db *sql.DB, matrix *matrix.Client) *Server {
return &Server{ return &Server{
config: config,
db: db,
matrix: matrix, matrix: matrix,
} }
} }
func (s Server) ListenAndServe(config *config.ServerConfig, ctx context.Context, wg *sync.WaitGroup) error { func (s Server) ListenAndServe(ctx context.Context, wg *sync.WaitGroup) error {
err := s.createTables()
if err != nil {
return err
}
http.HandleFunc("/", http.NotFound) http.HandleFunc("/", http.NotFound)
http.HandleFunc("/order_placed", s.orderPlaced) http.HandleFunc("/order_placed", s.orderPlaced)
server := http.Server{ server := http.Server{
Addr: config.ListenAddress, Addr: s.config.ListenAddress,
} }
go func() { go func() {
@ -43,9 +54,9 @@ func (s Server) ListenAndServe(config *config.ServerConfig, ctx context.Context,
} }
}() }()
log.Print("listening on ", config.ListenAddress) log.Print("listening on ", s.config.ListenAddress)
err := server.ListenAndServe() err = server.ListenAndServe()
if err != http.ErrServerClosed { if err != http.ErrServerClosed {
return err return err
} }
@ -53,22 +64,41 @@ func (s Server) ListenAndServe(config *config.ServerConfig, ctx context.Context,
return nil return nil
} }
func (s Server) orderPlaced(w http.ResponseWriter, r *http.Request) { func (s Server) createTables() error {
if r.Header.Get("Content-Type") != "application/json" { tables := []string{
writeStatus(w, http.StatusBadRequest) `CREATE TABLE IF NOT EXISTS orders (
return code TEXT PRIMARY KEY ON CONFLICT IGNORE
);
`,
} }
decoder := json.NewDecoder(r.Body) tx, err := s.db.Begin()
var data map[string]interface{}
err := decoder.Decode(&data)
if err != nil { if err != nil {
writeStatus(w, http.StatusBadRequest) return err
return
} }
log.Print(data) for _, table := range tables {
_, err := tx.Exec(table)
if err != nil {
if err := tx.Rollback(); err != nil {
log.Print(err)
}
return err
}
}
err = tx.Commit()
if err != nil {
return err
}
return nil
}
func (s Server) buildURL(endpoint string, args ...any) string {
baseURL := strings.TrimSuffix(s.config.BaseURL, "/")
return fmt.Sprintf(endpoint, append([]any{baseURL}, args...)...)
} }
func writeStatus(w http.ResponseWriter, code int) { func writeStatus(w http.ResponseWriter, code int) {

View File

@ -60,9 +60,9 @@ func main() {
go client.Sync(ctx, &wg) go client.Sync(ctx, &wg)
server := pretix.NewServer(client) server := pretix.NewServer(&config.Server, db, client)
err = server.ListenAndServe(&config.Server, ctx, &wg) err = server.ListenAndServe(ctx, &wg)
if err != nil { if err != nil {
stop() stop()
wg.Wait() wg.Wait()