Consolidate RSVP and donation into single endpoint
This commit is contained in:
75
main.go
75
main.go
@@ -76,7 +76,6 @@ func main() {
|
|||||||
http.HandleFunc("POST /auth/google/callback", handleGoogleCallback)
|
http.HandleFunc("POST /auth/google/callback", handleGoogleCallback)
|
||||||
http.HandleFunc("GET /api/rsvp/{eventID}", handleRSVPGet)
|
http.HandleFunc("GET /api/rsvp/{eventID}", handleRSVPGet)
|
||||||
http.HandleFunc("POST /api/rsvp/{eventID}", handleRSVPPost)
|
http.HandleFunc("POST /api/rsvp/{eventID}", handleRSVPPost)
|
||||||
http.HandleFunc("POST /api/donate/{eventID}", handleDonate)
|
|
||||||
http.HandleFunc("GET /api/donate/success/{eventID}", handleDonateSuccess)
|
http.HandleFunc("GET /api/donate/success/{eventID}", handleDonateSuccess)
|
||||||
http.HandleFunc("POST /api/stripe/webhook", handleStripeWebhook)
|
http.HandleFunc("POST /api/stripe/webhook", handleStripeWebhook)
|
||||||
|
|
||||||
@@ -218,46 +217,52 @@ func handleRSVPPost(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var req struct {
|
var req struct {
|
||||||
NumPeople int `json:"numPeople"`
|
NumPeople *int `json:"numPeople"`
|
||||||
|
DonationCents int64 `json:"donationCents"`
|
||||||
}
|
}
|
||||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||||
http.Error(w, "invalid json", http.StatusBadRequest)
|
http.Error(w, "invalid json", http.StatusBadRequest)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
_, err := db.Exec(`
|
|
||||||
INSERT INTO rsvps (event_id, google_username, num_people) VALUES ($1, $2, $3)
|
if req.NumPeople != nil {
|
||||||
ON CONFLICT (event_id, google_username) DO UPDATE SET num_people = $3
|
_, err := db.Exec(`
|
||||||
`, eventID, email, req.NumPeople)
|
INSERT INTO rsvps (event_id, google_username, num_people) VALUES ($1, $2, $3)
|
||||||
if err != nil {
|
ON CONFLICT (event_id, google_username) DO UPDATE SET num_people = $3
|
||||||
log.Println("[ERROR] failed to upsert rsvp:", err)
|
`, eventID, email, *req.NumPeople)
|
||||||
|
if err != nil {
|
||||||
|
log.Println("[ERROR] failed to upsert rsvp:", err)
|
||||||
|
http.Error(w, "database error", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var numPeople int
|
||||||
|
var donation float64
|
||||||
|
err := db.QueryRow("SELECT num_people, donation FROM rsvps WHERE event_id = $1 AND google_username = $2", eventID, email).Scan(&numPeople, &donation)
|
||||||
|
if err != nil && err != sql.ErrNoRows {
|
||||||
|
log.Println("[ERROR] failed to query rsvp:", err)
|
||||||
http.Error(w, "database error", http.StatusInternalServerError)
|
http.Error(w, "database error", http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
resp := map[string]any{"numPeople": numPeople, "donation": donation}
|
||||||
|
|
||||||
|
if req.DonationCents > 0 {
|
||||||
|
stripeURL, err := createCheckoutSession(eventID, email, req.DonationCents)
|
||||||
|
if err != nil {
|
||||||
|
log.Println("[ERROR] failed to create checkout session:", err)
|
||||||
|
http.Error(w, "failed to create checkout session", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
resp["url"] = stripeURL
|
||||||
|
}
|
||||||
|
|
||||||
w.Header().Set("Content-Type", "application/json")
|
w.Header().Set("Content-Type", "application/json")
|
||||||
json.NewEncoder(w).Encode(map[string]int{"numPeople": req.NumPeople})
|
json.NewEncoder(w).Encode(resp)
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleDonate(w http.ResponseWriter, r *http.Request) {
|
func createCheckoutSession(eventID, email string, amountCents int64) (string, error) {
|
||||||
eventID := r.PathValue("eventID")
|
|
||||||
email, ok := authorize(r)
|
|
||||||
if !ok {
|
|
||||||
http.Error(w, "unauthorized", http.StatusUnauthorized)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var req struct {
|
|
||||||
Amount int64 `json:"amount"`
|
|
||||||
}
|
|
||||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
|
||||||
http.Error(w, "invalid json", http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if req.Amount < 100 {
|
|
||||||
http.Error(w, "minimum donation is $1", http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
baseURL := os.Getenv("BASE_URL")
|
baseURL := os.Getenv("BASE_URL")
|
||||||
params := &stripe.CheckoutSessionParams{
|
params := &stripe.CheckoutSessionParams{
|
||||||
CustomerEmail: stripe.String(email),
|
CustomerEmail: stripe.String(email),
|
||||||
@@ -269,7 +274,7 @@ func handleDonate(w http.ResponseWriter, r *http.Request) {
|
|||||||
ProductData: &stripe.CheckoutSessionLineItemPriceDataProductDataParams{
|
ProductData: &stripe.CheckoutSessionLineItemPriceDataProductDataParams{
|
||||||
Name: stripe.String("Donation - Applause for a Cause"),
|
Name: stripe.String("Donation - Applause for a Cause"),
|
||||||
},
|
},
|
||||||
UnitAmount: stripe.Int64(req.Amount),
|
UnitAmount: stripe.Int64(amountCents),
|
||||||
},
|
},
|
||||||
Quantity: stripe.Int64(1),
|
Quantity: stripe.Int64(1),
|
||||||
},
|
},
|
||||||
@@ -284,13 +289,9 @@ func handleDonate(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
s, err := session.New(params)
|
s, err := session.New(params)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println("[ERROR] failed to create checkout session:", err)
|
return "", err
|
||||||
http.Error(w, "failed to create checkout session", http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
return s.URL, nil
|
||||||
w.Header().Set("Content-Type", "application/json")
|
|
||||||
json.NewEncoder(w).Encode(map[string]string{"url": s.URL})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func processPayment(sess *stripe.CheckoutSession) error {
|
func processPayment(sess *stripe.CheckoutSession) error {
|
||||||
|
|||||||
@@ -269,11 +269,15 @@
|
|||||||
}, true);
|
}, true);
|
||||||
|
|
||||||
setupButtonGroup('additional-donation-group', async val => {
|
setupButtonGroup('additional-donation-group', async val => {
|
||||||
|
let donationCents = 0;
|
||||||
if (val === 'custom') {
|
if (val === 'custom') {
|
||||||
const amount = parseInt(document.getElementById('additional-amount').value) || 0;
|
donationCents = (parseInt(document.getElementById('additional-amount').value) || 0) * 100;
|
||||||
if (amount > 0) await startDonation(amount);
|
|
||||||
} else {
|
} else {
|
||||||
await startDonation(parseInt(val));
|
donationCents = parseInt(val) * 100;
|
||||||
|
}
|
||||||
|
if (donationCents > 0) {
|
||||||
|
const data = await api('POST', `/api/rsvp/${eventId}`, { donationCents });
|
||||||
|
if (data.url) location.href = data.url;
|
||||||
}
|
}
|
||||||
}, true);
|
}, true);
|
||||||
|
|
||||||
@@ -306,21 +310,19 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function startDonation(amount) {
|
|
||||||
const data = await api('POST', `/api/donate/${eventId}`, { amount: amount * 100 });
|
|
||||||
location.href = data.url;
|
|
||||||
}
|
|
||||||
|
|
||||||
document.getElementById('rsvp-btn').addEventListener('click', async () => {
|
document.getElementById('rsvp-btn').addEventListener('click', async () => {
|
||||||
const data = await api('POST', `/api/rsvp/${eventId}`, { numPeople: selectedNumPeople });
|
let donationCents = 0;
|
||||||
updateUI(data);
|
if (selectedDonation === 'custom') {
|
||||||
|
donationCents = (parseInt(document.getElementById('custom-amount').value) || 0) * 100;
|
||||||
let donationAmount = selectedDonation;
|
} else if (selectedDonation > 0) {
|
||||||
if (donationAmount === 'custom') {
|
donationCents = selectedDonation * 100;
|
||||||
donationAmount = parseInt(document.getElementById('custom-amount').value) || 0;
|
|
||||||
}
|
}
|
||||||
if (donationAmount > 0) {
|
|
||||||
await startDonation(donationAmount);
|
const data = await api('POST', `/api/rsvp/${eventId}`, { numPeople: selectedNumPeople, donationCents });
|
||||||
|
if (data.url) {
|
||||||
|
location.href = data.url;
|
||||||
|
} else {
|
||||||
|
updateUI(data);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user