// // Created by vfs on 02.05.2024. // package main import ( "encoding/json" "errors" "fmt" "html/template" "io" "log" "net/http" "runtime/debug" "strings" "unicode/utf8" "github.com/stripe/stripe-go/v78" "github.com/stripe/stripe-go/v78/billingportal/session" "github.com/stripe/stripe-go/v78/customer" "github.com/stripe/stripe-go/v78/customersession" "github.com/stripe/stripe-go/v78/price" "github.com/stripe/stripe-go/v78/subscription" "github.com/stripe/stripe-go/v78/webhook" "alfheimgame.com/alfheim/pkg/models" ) //import "strconv" type TemplateData struct { AuthenticatedUser int32 FormErrors map[string]string Account models.Account Prices []stripe.Price ClientSecret string ActiveSubscription bool } func favicon(w http.ResponseWriter, r *http.Request) { http.ServeFile(w, r, "favicon.ico") } func authenticatedUser(w http.ResponseWriter, r *http.Request) int32 { session, err := store.Get(r, "id") if err != nil { log.Println(err) return 0 } id, ok := session.Values["id"].(int32) if !ok { trace := fmt.Sprintf("%s\n%s", errors.New("type assertion to int32 failed").Error(), debug.Stack()) _ = trace //log.Println(trace) return 0 } // check if the saved id exists in the database, otherwise it's a bad id and has to be removed from the cookies exists := users.ExistsAccount(id) if !exists { session, _ := store.Get(r, "id") session.Values["id"] = 0 session.Save(r, w) return 0 } return id } func home(w http.ResponseWriter, r *http.Request) { if r.URL.Path != "/" { http.NotFound(w, r) return } id := authenticatedUser(w, r) account, _ := users.GetAccount(id) active_subscription := subscriptions.HasActiveSubscription(id) text, err := template.ParseFiles("ui/base.html", "ui/index.html") if err != nil { http.Error(w, "Internal Server Error", 500) log.Println(err) } switch r.Method { case http.MethodGet: err := text.Execute(w, TemplateData{AuthenticatedUser: id, Account: account, ActiveSubscription: active_subscription}) if err != nil { log.Println(err) http.Error(w, "Internal Server Error", 500) } case http.MethodPost: err := text.Execute(w, TemplateData{}) if err != nil { log.Println(err) http.Error(w, "Internal Server Error", 500) } } } func login(w http.ResponseWriter, r *http.Request) { text, err := template.ParseFiles("ui/base.html", "ui/login.html") if err != nil { http.Error(w, "Internal Server Error", 500) log.Fatal(err) } switch r.Method { case http.MethodGet: err := text.Execute(w, TemplateData{}) if err != nil { log.Fatal(err) http.Error(w, "Internal Server Error", 500) } case http.MethodPost: session, _ := store.Get(r, "id") password := r.FormValue("password") username := r.FormValue("username") errors := make(map[string]string) if strings.TrimSpace(username) == "" { errors["username"] = "This field cannot be blank" } else if utf8.RuneCountInString(username) > 20 { errors["username"] = "This field is too long (the maximum is 20 characters)" } if strings.TrimSpace(password) == "" { errors["password"] = "This field cannot be blank" } else if utf8.RuneCountInString(password) < 8 { errors["password"] = "This field is too short (the minimum is 8 characters)" } if len(errors) > 0 { err := text.Execute(w, TemplateData{AuthenticatedUser: authenticatedUser(w, r), FormErrors: errors}) if err != nil { log.Println(err) http.Error(w, "Internal Server Error", 500) } return } id, err := users.Authenticate(username, password) if err == models.ErrInvalidCredentials { errors["generic"] = "Email or Password is incorrect" err := text.Execute(w, TemplateData{AuthenticatedUser: authenticatedUser(w, r), FormErrors: errors}) if err != nil { log.Println(err) http.Error(w, "Internal Server Error", 500) } return } if id > 0 { session.Values["id"] = id //log.Println("Logged in with id:", id) session.Save(r, w) http.Redirect(w, r, "/account", http.StatusSeeOther) } } } func logout(w http.ResponseWriter, r *http.Request) { text, err := template.ParseFiles("ui/base.html", "ui/logout.html") if err != nil { http.Error(w, "Internal Server Error", 500) log.Println(err) } id := authenticatedUser(w, r) account, err := users.GetAccount(id) if err != nil { http.Error(w, "Internal Server Error", 500) log.Println(err) } activesubscription := subscriptions.HasActiveSubscription(id) switch r.Method { case http.MethodGet: err := text.Execute(w, TemplateData{AuthenticatedUser: id, Account: account, ActiveSubscription: activesubscription}) if err != nil { log.Println(err) http.Error(w, "Internal Server Error", 500) } case http.MethodPost: session, _ := store.Get(r, "id") session.Values["id"] = 0 session.Save(r, w) http.Redirect(w, r, "/", http.StatusSeeOther) } } func register(w http.ResponseWriter, r *http.Request) { text, err := template.ParseFiles("ui/base.html", "ui/register.html") if err != nil { http.Error(w, "Internal Server Error", 500) log.Println(err) } id := authenticatedUser(w, r) account, _ := users.GetAccount(id) // if err != nil { // http.Error(w, "Internal Server Error", 500) // log.Println(err) // } switch r.Method { case http.MethodGet: err := text.Execute(w, TemplateData{AuthenticatedUser: id, Account: account}) if err != nil { log.Println(err) http.Error(w, "Internal Server Error", 500) } case http.MethodPost: account := models.Account{Username: r.FormValue("username"), Password: []byte(r.FormValue("password")), Firstname: r.FormValue("firstname"), Lastname: r.FormValue("lastname"), Email: r.FormValue("email")} errors := make(map[string]string) if strings.TrimSpace(account.Username) == "" { errors["username"] = "This field cannot be blank" } else if utf8.RuneCountInString(account.Username) > 20 { errors["username"] = "This field is too long (the maximum is 20 characters)" } if strings.TrimSpace(string(account.Password)) == "" { errors["password"] = "This field cannot be blank" } else if utf8.RuneCountInString(string(account.Password)) < 8 { errors["password"] = "This field is too short (the minimum is 8 characters)" } if len(errors) > 0 { err := text.Execute(w, TemplateData{AuthenticatedUser: authenticatedUser(w, r), FormErrors: errors}) if err != nil { log.Println(err) http.Error(w, "Internal Server Error", 500) } } _, err := users.Insert(account.Username, string(account.Password), account.Firstname, account.Lastname, account.Email) if err == models.ErrDuplicateEmail || err == models.ErrDuplicateUsername { } else if err != nil { } http.Redirect(w, r, "/login", http.StatusSeeOther) } } func account(w http.ResponseWriter, r *http.Request) { //id, err := strconv.Atoi(r.URL.Query().Get("id")) //if err != nil || id < 1 { // http.NotFound(w, r) // return //} //account, err := users.Get_account(int32(id)) //if err != nil { // log.Fatal(err) //} id := authenticatedUser(w, r) account, err := users.GetAccount(id) if err != nil { http.Error(w, "Internal Server Error", 500) log.Println(err) } //log.Println(id, account) text, err := template.ParseFiles("ui/base.html", "ui/account.html") if err != nil { http.Error(w, "Internal Server Error", 500) log.Println(err) } switch r.Method { case http.MethodGet: err := text.Execute(w, TemplateData{AuthenticatedUser: id, Account: account}) if err != nil { log.Println(err) http.Error(w, "Internal Server Error", 500) } } //case http.MethodPost: // text.Execute(w, false) // if err != nil { // log.Fatal(err) // http.Error(w, "Internal Server Error", 500) // } } func deleteAccount(w http.ResponseWriter, r *http.Request) { id := authenticatedUser(w, r) switch r.Method { case http.MethodPost: log.Println("Deleting account with id ", id) users.Delete(id) session, err := store.Get(r, "id") if err != nil { log.Println(err) http.Error(w, "Internal Server Error", 500) } session.Values["id"] = 0 session.Save(r, w) http.Redirect(w, r, "/", http.StatusSeeOther) } } func subscribe(w http.ResponseWriter, r *http.Request) { id := authenticatedUser(w, r) account, err := users.GetAccount(id) if err != nil { http.Error(w, "Internal Server Error", 500) log.Println(err) } params := &stripe.PriceListParams{} params.Limit = stripe.Int64(3) params.AddExpand("data.product") results := price.List(params) prices := make([]stripe.Price, 0) for results.Next() { //log.Println(results.Current()) prices = append(prices, *results.Price()) } fm := template.FuncMap{ "divide": func(a, b float64) float64 { return a / b }, } text, err := template.New("base.html").Funcs(fm).ParseFiles("ui/base.html", "ui/subscribe.html") if err != nil { http.Error(w, "Internal Server Error", 500) log.Println(err) return } err = text.Execute(w, TemplateData{AuthenticatedUser: id, Account: account, Prices: prices}) if err != nil { log.Println(err) http.Error(w, "Internal Server Error", 500) } } func subscribeStripe(w http.ResponseWriter, r *http.Request) { id := authenticatedUser(w, r) account, err := users.GetAccount(id) if err != nil { http.Error(w, "Internal Server Error", 500) log.Println(err) } params := &stripe.CustomerSessionParams{ Customer: stripe.String(account.StripeID), Components: &stripe.CustomerSessionComponentsParams{ PricingTable: &stripe.CustomerSessionComponentsPricingTableParams{ Enabled: stripe.Bool(true), }, }, } result, err := customersession.New(params) if err != nil { log.Println(err) } text, err := template.ParseFiles("ui/base.html", "ui/subscribe_stripe.html") if err != nil { http.Error(w, "Internal Server Error", 500) log.Println(err) } err = text.Execute(w, TemplateData{AuthenticatedUser: id, Account: account, ClientSecret: result.ClientSecret}) if err != nil { log.Println(err) http.Error(w, "Internal Server Error", 500) } } func manageBilling(w http.ResponseWriter, r *http.Request) { id := authenticatedUser(w, r) account, err := users.GetAccount(id) if err != nil { log.Println(err) } params := &stripe.BillingPortalSessionParams{ Customer: stripe.String(account.StripeID), ReturnURL: stripe.String("http://localhost:8080/account"), } result, err := session.New(params) if err != nil { log.Println(err) } http.Redirect(w, r, result.URL, http.StatusSeeOther) } func webhooks(w http.ResponseWriter, r *http.Request) { const MaxBodyBytes = int64(65536) r.Body = http.MaxBytesReader(w, r.Body, MaxBodyBytes) payload, err := io.ReadAll(r.Body) if err != nil { log.Printf("Error reading request body: %v\n", err) w.WriteHeader(http.StatusServiceUnavailable) return } endpointsecret := "whsec_43420f280f7695d9aa411c17da9ffac9afcecc3d36687035a8cb26f7f892f1cf" event, err := webhook.ConstructEvent(payload, r.Header.Get("Stripe-Signature"), endpointsecret) if err != nil { log.Printf("Error verifying webhook signature: %v\n", err) w.WriteHeader(http.StatusBadRequest) // Return a 400 error on a bad signature return } err = json.Unmarshal(payload, &event) if err != nil { log.Printf("Failed to parse webhook body json: %v\n", err.Error()) w.WriteHeader(http.StatusBadRequest) return } // Unmarshal the event data into an appropriate struct depending on its Type switch event.Type { case "checkout.session.completed": var checkoutSession stripe.CheckoutSession err := json.Unmarshal(event.Data.Raw, &checkoutSession) if err != nil { log.Printf("Error parsing webhook JSON: %v\n", err) w.WriteHeader(http.StatusBadRequest) return } // Then define and call a func to handle the successful payment intent. // handlePaymentIntentSucceeded(paymentIntent) handle_checkout_session_completed(checkoutSession) case "payment_intent.succeeded": var paymentIntent stripe.PaymentIntent err := json.Unmarshal(event.Data.Raw, &paymentIntent) if err != nil { log.Printf("Error parsing webhook JSON: %v\n", err) w.WriteHeader(http.StatusBadRequest) return } // Then define and call a func to handle the successful payment intent. // handlePaymentIntentSucceeded(paymentIntent) case "payment_method.attached": var paymentMethod stripe.PaymentMethod err := json.Unmarshal(event.Data.Raw, &paymentMethod) if err != nil { log.Printf("Error parsing webhook JSON: %v\n", err) w.WriteHeader(http.StatusBadRequest) return } case "setup_intent.succeeded": var setupIntent stripe.SetupIntent err := json.Unmarshal(event.Data.Raw, &setupIntent) if err != nil { log.Printf("Error parsing webhook JSON: %v\n", err) w.WriteHeader(http.StatusBadRequest) return } // ... handle other event types default: log.Printf("Unhandled event type: %s\n", event.Type) } w.WriteHeader(http.StatusOK) } func handle_checkout_session_completed(checkoutSession stripe.CheckoutSession) error { //toprint, _ := json.MarshalIndent(checkoutSession, "", " ") //log.Println(string(toprint)) subscription, err := subscription.Get(checkoutSession.Subscription.ID, nil) if err != nil { log.Println(err) return err } //log.Println(subscription.Customer.ID) //var status SubscriptionStatus //switch subscription.Status { // case "incomplete": // status = incomplete // case "incomplete_expired": // status = incomplete_expired // case "trialing": // status = trialing // case "active": // status = active // case "past_due": // status = past_due // case "canceled": // status = canceled // case "unpaid": // status = unpaid // case "paused": // status = paused //} subscriptions.Insert(checkoutSession.Customer.ID, subscription.ID, checkoutSession.ID, subscription.Status) return nil } func handlePaymentMethodAttached(paymentMethod stripe.PaymentMethod) error { //toprint, _ := json.MarshalIndent(setupintent, "", " ") //log.Println(string(toprint)) // make this the new customer's default payment method params := &stripe.CustomerParams{} params.DefaultSource = &paymentMethod.ID /*result*/ _, err := customer.Update(paymentMethod.Customer.ID, params) //log.Println(result) if err != nil { log.Println(err) return err } return nil }