Add /api/donate endpoint for anonymous donations
This commit is contained in:
45
main.go
45
main.go
@@ -113,6 +113,7 @@ 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)
|
||||||
http.HandleFunc("GET /api/report", handleReport)
|
http.HandleFunc("GET /api/report", handleReport)
|
||||||
@@ -322,14 +323,37 @@ func handleRSVPPost(w http.ResponseWriter, r *http.Request) {
|
|||||||
json.NewEncoder(w).Encode(resp)
|
json.NewEncoder(w).Encode(resp)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func handleDonate(w http.ResponseWriter, r *http.Request) {
|
||||||
|
eventID := r.PathValue("eventID")
|
||||||
|
|
||||||
|
var req struct {
|
||||||
|
DonationCents int64 `json:"donationCents"`
|
||||||
|
}
|
||||||
|
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||||
|
http.Error(w, "invalid json", http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if req.DonationCents <= 0 {
|
||||||
|
http.Error(w, "donation amount required", http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
stripeURL, err := createCheckoutSession(eventID, "", req.DonationCents, 0)
|
||||||
|
if err != nil {
|
||||||
|
log.Println("[ERROR] failed to create checkout session:", err)
|
||||||
|
http.Error(w, "failed to create checkout session", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
json.NewEncoder(w).Encode(map[string]any{"url": stripeURL})
|
||||||
|
}
|
||||||
|
|
||||||
func createCheckoutSession(eventID, email string, amountCents int64, numPeople int) (string, error) {
|
func createCheckoutSession(eventID, email string, amountCents int64, numPeople int) (string, error) {
|
||||||
baseURL := os.Getenv("BASE_URL")
|
baseURL := os.Getenv("BASE_URL")
|
||||||
params := &stripe.CheckoutSessionParams{
|
params := &stripe.CheckoutSessionParams{
|
||||||
CustomerEmail: stripe.String(email),
|
Mode: stripe.String(string(stripe.CheckoutSessionModePayment)),
|
||||||
Mode: stripe.String(string(stripe.CheckoutSessionModePayment)),
|
|
||||||
PaymentIntentData: &stripe.CheckoutSessionPaymentIntentDataParams{
|
|
||||||
ReceiptEmail: stripe.String(email),
|
|
||||||
},
|
|
||||||
LineItems: []*stripe.CheckoutSessionLineItemParams{
|
LineItems: []*stripe.CheckoutSessionLineItemParams{
|
||||||
{
|
{
|
||||||
PriceData: &stripe.CheckoutSessionLineItemPriceDataParams{
|
PriceData: &stripe.CheckoutSessionLineItemPriceDataParams{
|
||||||
@@ -346,10 +370,16 @@ func createCheckoutSession(eventID, email string, amountCents int64, numPeople i
|
|||||||
CancelURL: stripe.String(fmt.Sprintf("%s/%s", baseURL, eventID)),
|
CancelURL: stripe.String(fmt.Sprintf("%s/%s", baseURL, eventID)),
|
||||||
Metadata: map[string]string{
|
Metadata: map[string]string{
|
||||||
"event_id": eventID,
|
"event_id": eventID,
|
||||||
"email": email,
|
|
||||||
"num_people": fmt.Sprintf("%d", numPeople),
|
"num_people": fmt.Sprintf("%d", numPeople),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
if email != "" {
|
||||||
|
params.CustomerEmail = stripe.String(email)
|
||||||
|
params.PaymentIntentData = &stripe.CheckoutSessionPaymentIntentDataParams{
|
||||||
|
ReceiptEmail: stripe.String(email),
|
||||||
|
}
|
||||||
|
params.Metadata["email"] = email
|
||||||
|
}
|
||||||
|
|
||||||
s, err := session.New(params)
|
s, err := session.New(params)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -365,6 +395,9 @@ func processPayment(sess *stripe.CheckoutSession) error {
|
|||||||
|
|
||||||
eventID := sess.Metadata["event_id"]
|
eventID := sess.Metadata["event_id"]
|
||||||
email := sess.Metadata["email"]
|
email := sess.Metadata["email"]
|
||||||
|
if email == "" && sess.CustomerDetails != nil {
|
||||||
|
email = sess.CustomerDetails.Email
|
||||||
|
}
|
||||||
amount := float64(sess.AmountTotal) / 100
|
amount := float64(sess.AmountTotal) / 100
|
||||||
|
|
||||||
tx, err := db.Begin()
|
tx, err := db.Begin()
|
||||||
|
|||||||
@@ -117,9 +117,6 @@
|
|||||||
<wa-card>
|
<wa-card>
|
||||||
<div class="event-header">
|
<div class="event-header">
|
||||||
<img src="/afac26-logo.png" alt="Applause for a Cause">
|
<img src="/afac26-logo.png" alt="Applause for a Cause">
|
||||||
<p>Saturday, February 7, 2026 · 6:30 PM</p>
|
|
||||||
<p>Helios Gym</p>
|
|
||||||
<p>597 Central Avenue, Sunnyvale, CA 94086</p>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="info-box">
|
<div class="info-box">
|
||||||
<p style="margin: 0;">All donations go to <a href="https://svcommunityservices.org/" target="_blank">Sunnyvale Community Services</a>. Thank you for your generosity!</p>
|
<p style="margin: 0;">All donations go to <a href="https://svcommunityservices.org/" target="_blank">Sunnyvale Community Services</a>. Thank you for your generosity!</p>
|
||||||
@@ -127,10 +124,10 @@
|
|||||||
<div class="donation-section">
|
<div class="donation-section">
|
||||||
<div style="font-weight: bold; margin-bottom: 1rem;">Select a donation amount:</div>
|
<div style="font-weight: bold; margin-bottom: 1rem;">Select a donation amount:</div>
|
||||||
<div class="button-group donation-group" id="donation-group">
|
<div class="button-group donation-group" id="donation-group">
|
||||||
<wa-button variant="neutral" class="selected" data-value="25">$25</wa-button>
|
<wa-button variant="neutral" class="selected" data-value="10">$10</wa-button>
|
||||||
|
<wa-button variant="neutral" data-value="25">$25</wa-button>
|
||||||
<wa-button variant="neutral" data-value="50">$50</wa-button>
|
<wa-button variant="neutral" data-value="50">$50</wa-button>
|
||||||
<wa-button variant="neutral" data-value="custom"><span>Other</span><wa-input type="number" size="small" id="custom-amount" min="1" value="100"><span slot="start">$</span></wa-input></wa-button>
|
<wa-button variant="neutral" data-value="custom"><span>Other</span><wa-input type="number" size="small" id="custom-amount" min="1" value="100"><span slot="start">$</span></wa-input></wa-button>
|
||||||
<wa-button variant="neutral" data-value="100">$100</wa-button>
|
|
||||||
</div>
|
</div>
|
||||||
<div style="text-align: center; margin-top: 1.5rem;">
|
<div style="text-align: center; margin-top: 1.5rem;">
|
||||||
<wa-button variant="brand" id="donate-btn">Donate</wa-button>
|
<wa-button variant="brand" id="donate-btn">Donate</wa-button>
|
||||||
@@ -152,7 +149,7 @@
|
|||||||
history.replaceState({}, '', location.pathname);
|
history.replaceState({}, '', location.pathname);
|
||||||
}
|
}
|
||||||
|
|
||||||
let selectedDonation = 25;
|
let selectedDonation = 10;
|
||||||
|
|
||||||
function setupButtonGroup(groupId, onChange) {
|
function setupButtonGroup(groupId, onChange) {
|
||||||
const group = document.getElementById(groupId);
|
const group = document.getElementById(groupId);
|
||||||
|
|||||||
Reference in New Issue
Block a user