package main import ( "encoding/json" "errors" "fmt" "myapp/internal/cards" "myapp/internal/cards/encryption" "myapp/internal/models" "myapp/internal/urlsigner" "net/http" "strconv" "strings" "time" "github.com/go-chi/chi/v5" "github.com/stripe/stripe-go/v79" "golang.org/x/crypto/bcrypt" ) type stripePayload struct { Currency string `json:"currency"` Amount string `json:"amount"` PaymentMethod string `json:"payment_method"` Email string `json:"email"` CardBrand string `json:"card_brand"` ExpiryMonth int `json:"expiry_month"` ExpiryYear int `json:"expiry_year"` LastFour string `json:"last_four"` Plan string `json:"plan"` ProductID string `json:"product_id"` FirstName string `json:"first_name"` LastName string `json:"last_name"` } type jsonResponse struct { OK bool `json:"ok"` Message string `json:"message,omitempty"` Content string `json:"content,omitempty"` ID int `json:"id,omitempty"` } func (app *application) GetPaymentIntent(w http.ResponseWriter, r *http.Request) { var payload stripePayload err := json.NewDecoder(r.Body).Decode(&payload) if err != nil { app.errorLog.Println(err) return // TODO: return a valid json } amount, err := strconv.Atoi(payload.Amount) if err != nil { app.errorLog.Println(err) return // TODO: return a valid json } card := cards.Card{ Secret: app.config.stripe.secret, Key: app.config.stripe.key, Currency: payload.Currency, } pi, msg, err := card.Charge(payload.Currency, amount) if err != nil { j := jsonResponse{ OK: false, Message: msg, Content: "", } out, err := json.MarshalIndent(j, "", " ") if err != nil { app.errorLog.Println(err) } w.Header().Set("Content-Type", "application/json") w.Write(out) return } out, err := json.MarshalIndent(pi, "", " ") if err != nil { app.errorLog.Println(err) return // TODO: return a valid json } w.Header().Set("Content-Type", "application/json") w.Write(out) } func (app *application) GetWidgetByID(w http.ResponseWriter, r *http.Request) { id := chi.URLParam(r, "id") widgetID, _ := strconv.Atoi(id) widget, err := app.DB.GetWidget(widgetID) if err != nil { app.errorLog.Println(err) } out, err := json.MarshalIndent(widget, "", " ") if err != nil { app.errorLog.Println(err) } w.Header().Set("Content-Type", "application/json") w.Write(out) } func (app *application) CreateCustomerAndSubscribeToPlan(w http.ResponseWriter, r *http.Request) { var data stripePayload err := json.NewDecoder(r.Body).Decode(&data) if err != nil { app.errorLog.Println(err) return } app.infoLog.Println(data.Email, data.LastFour, data.PaymentMethod, data.Plan) card := cards.Card{ Secret: app.config.stripe.secret, Key: app.config.stripe.key, Currency: data.Currency, } okay := true var subscription *stripe.Subscription txnMsg := "Transaction successful" stripeCustomer, msg, err := card.CreateCustomer(data.PaymentMethod, data.Email) if err != nil { app.errorLog.Println(err) okay = false txnMsg = msg } if okay { subscription, err = card.SubscribeToPlan( stripeCustomer, data.Plan, data.Email, data.LastFour, "", ) if err != nil { app.errorLog.Println(err) okay = false txnMsg = "Error subscribing customer" } app.infoLog.Println("subscription id is", subscription.ID) } if okay { productID, _ := strconv.Atoi(data.ProductID) customerID, err := app.SaveCustomer(data.FirstName, data.LastName, data.Email) if err != nil { app.errorLog.Println(err) return } // create a new txn amount, _ := strconv.Atoi(data.Amount) txn := models.Transaction{ Amount: amount, Currency: "eur", LastFour: data.LastFour, ExpiryMonth: data.ExpiryMonth, ExpiryYear: data.ExpiryYear, TransactionStatusID: 2, } txnID, err := app.SaveTransaction(txn) if err != nil { app.errorLog.Println(err) return } // create order order := models.Order{ WidgetID: productID, TransactionID: txnID, CustomerID: customerID, StatusID: 1, Quantity: 1, Amount: amount, } _, err = app.SaveOrder(order) if err != nil { app.errorLog.Println(err) return } } resp := jsonResponse{ OK: okay, Message: txnMsg, } out, err := json.MarshalIndent(resp, "", " ") if err != nil { app.errorLog.Println(err) return } w.Header().Set("Content-Type", "application/json") w.Write(out) } func (app *application) SaveCustomer(firstName, lastName, email string) (int, error) { customer := models.Customer{ FirstName: firstName, LastName: lastName, Email: email, } id, err := app.DB.InsertCustomer(customer) if err != nil { return 0, err } return id, nil } func (app *application) SaveTransaction(txn models.Transaction) (int, error) { txnID, err := app.DB.InsertTransaction(txn) if err != nil { app.errorLog.Println(err) return 0, err } return txnID, nil } func (app *application) SaveOrder(order models.Order) (int, error) { id, err := app.DB.InsertOrder(order) if err != nil { app.errorLog.Println(err) return 0, err } return id, nil } func (app *application) CreateAuthToken(w http.ResponseWriter, r *http.Request) { var userInput struct { Email string `json:"email"` Password string `json:"password"` } err := app.readJSON(w, r, &userInput) if err != nil { app.errorLog.Println(err) app.badRequest(w, r, err) return } // get the user from the db by email, send error if invalid email user, err := app.DB.GetUserByEmail(userInput.Email) if err != nil { app.errorLog.Println(err) app.invalidCredentials(w) return } // validate the password, send error if invalid password validPassword, err := app.passwordMatches(user.Password, userInput.Password) if err != nil { app.errorLog.Println(err) app.invalidCredentials(w) return } if !validPassword { app.invalidCredentials(w) return } // generate the token token, err := models.GenerateToken(user.ID, 24*time.Hour, models.ScopeAuthentication) if err != nil { app.errorLog.Println(err) app.badRequest(w, r, err) return } // save to DB err = app.DB.InsertToken(token, user) if err != nil { app.errorLog.Println(err) app.badRequest(w, r, err) return } // send response var payload struct { OK bool `json:"ok"` Message string `json:"message"` Token *models.Token `json:"authentication_token"` } payload.OK = true payload.Message = fmt.Sprintf("token for %s created", userInput.Email) payload.Token = token _ = app.writeJSON(w, http.StatusOK, payload) } func (app *application) authenticateToken(r *http.Request) (*models.User, error) { authorizationHeader := r.Header.Get("Authorization") if authorizationHeader == "" { return nil, errors.New("no authorization header received") } headerParts := strings.Split(authorizationHeader, " ") if len(headerParts) != 2 || headerParts[0] != "Bearer" { return nil, errors.New("no authorization header received") } token := headerParts[1] if len(token) != 26 { return nil, errors.New("authentication token wrong size") } // get the user from the tokens table user, err := app.DB.GetUserForToken(token) if err != nil { return nil, errors.New("no matching user found") } return user, nil } func (app *application) CheckAuthentication(w http.ResponseWriter, r *http.Request) { // validate the token, and get associated user user, err := app.authenticateToken(r) if err != nil { app.errorLog.Println(err) app.invalidCredentials(w) return } // valid user var payload jsonResponse payload.OK = true payload.Message = fmt.Sprintf("authenticated user %s", user.Email) app.writeJSON(w, http.StatusOK, payload) } func (app *application) VirtualTerminalPaymentSucceeded(w http.ResponseWriter, r *http.Request) { var txnData struct { PaymentAmount int `json:"amount"` PaymentCurrency string `json:"currency"` FirstName string `json:"first_name"` LastName string `json:"last_name"` Email string `json:"email"` PaymentIntent string `json:"payment_intent"` PaymentMethod string `json:"payment_method"` BankReturnCode string `json:"bank_return_code"` ExpiryMonth int `json:"expiry_month"` ExpiryYear int `json:"expiry_year"` LastFour string `json:"last_four"` } err := app.readJSON(w, r, &txnData) if err != nil { app.badRequest(w, r, err) return } card := cards.Card{ Secret: app.config.stripe.secret, Key: app.config.stripe.key, } pi, err := card.RetrievePaymentIntent(txnData.PaymentIntent) if err != nil { app.badRequest(w, r, err) return } pm, err := card.GetPaymentMethod(txnData.PaymentMethod) if err != nil { app.badRequest(w, r, err) return } txnData.LastFour = pm.Card.Last4 txnData.ExpiryMonth = int(pm.Card.ExpMonth) txnData.ExpiryYear = int(pm.Card.ExpYear) txn := models.Transaction{ Amount: txnData.PaymentAmount, Currency: txnData.PaymentCurrency, LastFour: txnData.LastFour, ExpiryMonth: txnData.ExpiryMonth, ExpiryYear: txnData.ExpiryYear, BankReturnCode: pi.LatestCharge.ID, PaymentIntent: txnData.PaymentIntent, PaymentMethod: txnData.PaymentMethod, TransactionStatusID: 2, } _, err = app.SaveTransaction(txn) if err != nil { app.badRequest(w, r, err) } app.writeJSON(w, http.StatusOK, txn) } func (app *application) SendPasswordResetEmail(w http.ResponseWriter, r *http.Request) { var payload struct { Email string `json:"email"` } err := app.readJSON(w, r, &payload) if err != nil { app.badRequest(w, r, err) return } // verify that email exists _, err = app.DB.GetUserByEmail(payload.Email) if err != nil { resp := jsonResponse{ OK: false, Message: "No matching email found on our system", } app.writeJSON(w, http.StatusAccepted, resp) return } link := fmt.Sprintf("%s/reset-password?email=%s", app.config.frontend, payload.Email) sign := urlsigner.Signer{ Secret: []byte(app.config.secretkey), } signedLink := sign.GenerateTokenFromString(link) var data struct { Link string } data.Link = signedLink // send mail err = app.SendMail( "info@widgets.com", payload.Email, "Password Reset Request", "password-reset", data, ) if err != nil { app.errorLog.Println(err) app.badRequest(w, r, err) return } resp := jsonResponse{ OK: true, } app.writeJSON(w, http.StatusCreated, resp) } func (app *application) ResetPassword(w http.ResponseWriter, r *http.Request) { var payload struct { Email string `json:"email"` Password string `json:"password"` } err := app.readJSON(w, r, &payload) if err != nil { app.errorLog.Println(err) app.badRequest(w, r, err) return } encryptor := encryption.Encryption{ Key: []byte(app.config.secretkey), } realEmail, err := encryptor.Decrypt(payload.Email) if err != nil { app.errorLog.Println(err) app.badRequest(w, r, err) return } user, err := app.DB.GetUserByEmail(realEmail) if err != nil { app.errorLog.Println(err) app.badRequest(w, r, err) return } newHash, err := bcrypt.GenerateFromPassword([]byte(payload.Password), 12) if err != nil { app.errorLog.Println(err) app.badRequest(w, r, err) return } err = app.DB.UpdatePasswordForUser(user, string(newHash)) if err != nil { app.badRequest(w, r, err) return } resp := jsonResponse{ OK: true, Message: "Password reset.", } app.writeJSON(w, http.StatusCreated, resp) } func (app *application) AllSales(w http.ResponseWriter, r *http.Request) { allSales, err := app.DB.GetAllOrders(false) if err != nil { app.badRequest(w, r, err) } app.writeJSON(w, http.StatusOK, allSales) } func (app *application) AllSubscriptions(w http.ResponseWriter, r *http.Request) { allSubscriptions, err := app.DB.GetAllOrders(true) if err != nil { app.badRequest(w, r, err) } app.writeJSON(w, http.StatusOK, allSubscriptions) } func (app *application) GetSale(w http.ResponseWriter, r *http.Request) { id := chi.URLParam(r, "id") orderID, _ := strconv.Atoi(id) order, err := app.DB.GetOrderByID(orderID) if err != nil { app.badRequest(w, r, err) } app.writeJSON(w, http.StatusOK, order) } func (app *application) RefundCharge(w http.ResponseWriter, r *http.Request) { var chargeToRefund struct { ID int `json:"id"` PaymentIntent string `json:"pi"` Amount int `json:"amount"` Currency string `json:"currency"` } err := app.readJSON(w, r, &chargeToRefund) if err != nil { app.errorLog.Println(err) app.badRequest(w, r, err) return } // validate card := cards.Card{ Secret: app.config.stripe.secret, Key: app.config.stripe.key, Currency: chargeToRefund.Currency, } err = card.Refund(chargeToRefund.PaymentIntent, chargeToRefund.Amount) if err != nil { app.errorLog.Println(err) app.badRequest(w, r, err) return } var resp jsonResponse resp.OK = true resp.Message = "Charge refunded" app.writeJSON(w, http.StatusOK, resp) }