Implement order placed webhook
This commit is contained in:
parent
36d98f365a
commit
03e1e564f6
|
@ -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"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
|
@ -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) {
|
||||||
|
|
4
main.go
4
main.go
|
@ -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()
|
||||||
|
|
Loading…
Reference in New Issue