package pretix import ( "encoding/json" "fmt" "net/http" "strconv" "strings" "time" "maunium.net/go/mautrix/event" ) 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 } Total 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 numFree := 0 numPaid := 0 for _, position := range order.Positions { euros, cents, err := parsePrice(position.Price) if err != nil { continue } if euros > 0 || cents > 0 { numPaid++ } else { numFree++ } } var verb, free, conjunction, paid, noun, total string if numFree+numPaid == 1 { verb = "wurde " } else { verb = "wurden " } if numFree == 1 { free = "ein kostenloses " } else if numFree > 1 { free = fmt.Sprint(numFree, " kostenlose ") } if numPaid == 1 { paid = "ein bezahltes " } else if numPaid > 1 { paid = fmt.Sprint(numPaid, " bezahlte ") } if numFree > 0 && numPaid > 0 { conjunction = "und " } if numPaid == 1 || numPaid == 0 && numFree == 1 { // match number of adjective immediately before noun noun = "Ticket " } else { noun = "Tickets " } if totalEuros, totalCents, err := parsePrice(order.Total); err == nil && (totalEuros > 0 || totalCents > 0) { var adverb, fraction string if numPaid > 1 { adverb = "insgesamt " } if totalCents > 0 { fraction = fmt.Sprintf(",%02d", totalCents) } total = fmt.Sprint("für ", adverb, totalEuros, fraction, " Euro ") } message := event.MessageEventContent{ MsgType: event.MsgText, Body: fmt.Sprint("\U0001f389 Es ", verb, free, conjunction, paid, noun, total, "bestellt."), } success := s.matrix.Broadcast(&message) if !success { writeStatus(w, http.StatusInternalServerError) } } } func parsePrice(price string) (euros uint64, cents uint64, err error) { parts := strings.Split(price, ".") euros, err = strconv.ParseUint(parts[0], 10, 64) if err != nil { return 0, 0, err } if len(parts) > 1 { for len(parts[1]) < 2 { parts[1] += "0" } parts[1] = parts[1][:2] parts[1] = strings.TrimLeft(parts[1], "0") if parts[1] == "" { parts[1] = "0" } cents, err = strconv.ParseUint(parts[1], 10, 64) if err != nil { return 0, 0, err } } return euros, cents, nil }