save orders

This commit is contained in:
vinchent 2024-08-10 11:10:27 +02:00
parent 88be25d77f
commit 8e482aa712
8 changed files with 172 additions and 17 deletions

View File

@ -2,12 +2,19 @@ package main
import ( import (
"myapp/internal/cards" "myapp/internal/cards"
"myapp/internal/models"
"net/http" "net/http"
"strconv" "strconv"
"github.com/go-chi/chi/v5" "github.com/go-chi/chi/v5"
) )
func (app *application) Home(w http.ResponseWriter, r *http.Request) {
if err := app.renderTemplate(w, r, "home", &templateData{}); err != nil {
app.errorLog.Println(err)
}
}
func (app *application) VirtualTerminal(w http.ResponseWriter, r *http.Request) { func (app *application) VirtualTerminal(w http.ResponseWriter, r *http.Request) {
if err := app.renderTemplate(w, r, "terminal", &templateData{}); err != nil { if err := app.renderTemplate(w, r, "terminal", &templateData{}); err != nil {
app.errorLog.Println(err) app.errorLog.Println(err)
@ -22,12 +29,14 @@ func (app *application) PaymentSucceeded(w http.ResponseWriter, r *http.Request)
} }
// read posted data // read posted data
cardHolder := r.Form.Get("cardholder_name") firstName := r.Form.Get("first_name")
lastName := r.Form.Get("last_name")
email := r.Form.Get("cardholder_email") email := r.Form.Get("cardholder_email")
paymentIntent := r.Form.Get("payment_intent") paymentIntent := r.Form.Get("payment_intent")
paymentMethod := r.Form.Get("payment_method") paymentMethod := r.Form.Get("payment_method")
paymentAmount := r.Form.Get("payment_amount") paymentAmount := r.Form.Get("payment_amount")
paymentCurrency := r.Form.Get("payment_currency") paymentCurrency := r.Form.Get("payment_currency")
widgetID, _ := strconv.Atoi(r.Form.Get("product_id"))
card := cards.Card{ card := cards.Card{
Secret: app.config.stripe.secret, Secret: app.config.stripe.secret,
@ -50,8 +59,47 @@ func (app *application) PaymentSucceeded(w http.ResponseWriter, r *http.Request)
expiryMonth := pm.Card.ExpMonth expiryMonth := pm.Card.ExpMonth
expiryYear := pm.Card.ExpYear expiryYear := pm.Card.ExpYear
customerID, err := app.SaveCustomer(firstName, lastName, email)
if err != nil {
app.errorLog.Println(err)
return
}
app.infoLog.Printf("custumer id: %d", customerID)
amount, _ := strconv.Atoi(paymentAmount)
transaction := models.Transaction{
Amount: amount,
Currency: paymentCurrency,
LastFour: lastFour,
ExpiryMonth: int(expiryMonth),
ExpiryYear: int(expiryYear),
BankReturnCode: pi.LatestCharge.ID,
TransactionStatusID: 2, // TODO: use an enum
}
txnID, err := app.SaveTransaction(transaction)
if err != nil {
app.errorLog.Println(err)
return
}
app.infoLog.Printf("transaction id: %d", txnID)
order := models.Order{
WidgetID: widgetID,
TransactionID: txnID,
CustomerID: customerID,
StatusID: 1,
Quantity: 1,
Amount: amount,
}
orderID, err := app.SaveOrder(order)
if err != nil {
app.errorLog.Println(err)
return
}
app.infoLog.Printf("order id: %d", orderID)
data := make(map[string]interface{}) data := make(map[string]interface{})
data["cardholder"] = cardHolder
data["email"] = email data["email"] = email
data["pi"] = paymentIntent data["pi"] = paymentIntent
data["pm"] = paymentMethod data["pm"] = paymentMethod
@ -61,6 +109,10 @@ func (app *application) PaymentSucceeded(w http.ResponseWriter, r *http.Request)
data["expiry_month"] = expiryMonth data["expiry_month"] = expiryMonth
data["expiry_year"] = expiryYear data["expiry_year"] = expiryYear
data["bank_return_code"] = pi.LatestCharge.ID data["bank_return_code"] = pi.LatestCharge.ID
data["first_name"] = firstName
data["last_name"] = lastName
// should write this data to session, and then redirect user to the new page
if err := app.renderTemplate(w, r, "succeeded", &templateData{ if err := app.renderTemplate(w, r, "succeeded", &templateData{
Data: data, Data: data,
@ -69,6 +121,41 @@ func (app *application) PaymentSucceeded(w http.ResponseWriter, r *http.Request)
} }
} }
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
}
// ChargeOnce displays the page to buy one widget // ChargeOnce displays the page to buy one widget
func (app *application) ChargeOnce(w http.ResponseWriter, r *http.Request) { func (app *application) ChargeOnce(w http.ResponseWriter, r *http.Request) {
id := chi.URLParam(r, "id") id := chi.URLParam(r, "id")

View File

@ -10,6 +10,8 @@ import (
"net/http" "net/http"
"os" "os"
"time" "time"
"github.com/alexedwards/scs/v2"
) )
const ( const (
@ -17,6 +19,8 @@ const (
cssVersion = "1" cssVersion = "1"
) )
var session *scs.SessionManager
type config struct { type config struct {
port int port int
env string env string
@ -37,6 +41,7 @@ type application struct {
templateCache map[string]*template.Template templateCache map[string]*template.Template
version string version string
DB models.DBModel DB models.DBModel
Session *scs.SessionManager
} }
func (app *application) serve() error { func (app *application) serve() error {
@ -83,6 +88,10 @@ func main() {
infoLog := log.New(os.Stdout, "INFO\t", log.Ldate|log.Ltime) infoLog := log.New(os.Stdout, "INFO\t", log.Ldate|log.Ltime)
errorLog := log.New(os.Stdout, "ERROR\t", log.Ldate|log.Ltime|log.Lshortfile) errorLog := log.New(os.Stdout, "ERROR\t", log.Ldate|log.Ltime|log.Lshortfile)
// set up session
session = scs.New()
session.Lifetime = 24 * time.Hour
conn, err := driver.OpenDB(cfg.db.dsn) conn, err := driver.OpenDB(cfg.db.dsn)
if err != nil { if err != nil {
errorLog.Fatal(err) errorLog.Fatal(err)
@ -98,6 +107,7 @@ func main() {
templateCache: tc, templateCache: tc,
version: version, version: version,
DB: models.DBModel{DB: conn}, DB: models.DBModel{DB: conn},
Session: session,
} }
app.infoLog.Println("Connected to MariaDB") app.infoLog.Println("Connected to MariaDB")

View File

@ -8,7 +8,9 @@ import (
func (app *application) routes() http.Handler { func (app *application) routes() http.Handler {
mux := chi.NewRouter() mux := chi.NewRouter()
mux.Use(SessionLoad)
mux.Get("/", app.Home)
mux.Get("/virtual-terminal", app.VirtualTerminal) mux.Get("/virtual-terminal", app.VirtualTerminal)
mux.Post("/payment-succeeded", app.PaymentSucceeded) mux.Post("/payment-succeeded", app.PaymentSucceeded)
mux.Get("/widget/{id}", app.ChargeOnce) mux.Get("/widget/{id}", app.ChargeOnce)

View File

@ -23,7 +23,33 @@ Buy one widget
<p class="mt-2 mb-3 text-center">{{$widget.Description}}</p> <p class="mt-2 mb-3 text-center">{{$widget.Description}}</p>
<hr> <hr>
<div class="mb-3"> <div class="mb-3">
<label for="cardholder-name" class="form-label">Cardholder Name</label> <label for="first-name" class="form-label">First Name</label>
<input type="text"
id="first-name"
name="first_name"
autocomplete="first-name-new"
required=""
class="form-control">
</div>
<div class="mb-3">
<label for="last-name" class="form-label">Last Name</label>
<input type="text"
id="last-name"
name="last_name"
autocomplete="last-name-new"
required=""
class="form-control">
</div>
<div class="mb-3">
<label for="cardholder-email" class="form-label">Email</label>
<input type="text"
id="cardholder-email"
name="cardholder_email"
autocomplete="cardholder-email-new"
required=""
class="form-control">
<div class="mb-3">
<label for="cardholder-name" class="form-label">Name on card</label>
<input type="text" <input type="text"
id="cardholder-name" id="cardholder-name"
name="cardholder_name" name="cardholder_name"
@ -31,14 +57,6 @@ Buy one widget
required="" required=""
class="form-control"> class="form-control">
</div> </div>
<div class="mb-3">
<label for="cardholder-email" class="form-label">Cardholder Email</label>
<input type="text"
id="cardholder-email"
name="cardholder_email"
autocomplete="cardholder-email-new"
required=""
class="form-control">
</div> </div>
<!-- card number will be built by stripe --> <!-- card number will be built by stripe -->
<div class="mb-3"> <div class="mb-3">

View File

@ -6,7 +6,7 @@ Payment Succedded!
<h2 class="mt-5">Payment Succeeded</h2> <h2 class="mt-5">Payment Succeeded</h2>
<hr> <hr>
<p>Payment Intent: {{ index .Data "pi" }}</p> <p>Payment Intent: {{ index .Data "pi" }}</p>
<p>Cardholder: {{ index .Data "cardholder" }}</p> <p>Customer Name: {{ index .Data "first_name" }} {{ index .Data "last_name" }}</p>
<p>Email: {{ index .Data "email" }}</p> <p>Email: {{ index .Data "email" }}</p>
<p>Payment Method: {{ index .Data "pm" }}</p> <p>Payment Method: {{ index .Data "pm" }}</p>
<p>Payment Amount: {{ index .Data "pa" }}</p> <p>Payment Amount: {{ index .Data "pa" }}</p>

5
go.mod
View File

@ -9,4 +9,7 @@ require (
github.com/stripe/stripe-go/v79 v79.6.0 github.com/stripe/stripe-go/v79 v79.6.0
) )
require filippo.io/edwards25519 v1.1.0 // indirect require (
filippo.io/edwards25519 v1.1.0 // indirect
github.com/alexedwards/scs/v2 v2.8.0 // indirect
)

2
go.sum
View File

@ -1,5 +1,7 @@
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
github.com/alexedwards/scs/v2 v2.8.0 h1:h31yUYoycPuL0zt14c0gd+oqxfRwIj6SOjHdKRZxhEw=
github.com/alexedwards/scs/v2 v2.8.0/go.mod h1:ToaROZxyKukJKT/xLcVQAChi5k6+Pn1Gvmdl7h3RRj8=
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/go-chi/chi/v5 v5.1.0 h1:acVI1TYaD+hhedDJ3r54HyA6sExp3HfXq7QWEEY/xMw= github.com/go-chi/chi/v5 v5.1.0 h1:acVI1TYaD+hhedDJ3r54HyA6sExp3HfXq7QWEEY/xMw=

View File

@ -70,6 +70,8 @@ type Transaction struct {
Amount int `json:"amount"` Amount int `json:"amount"`
Currency string `json:"currency"` Currency string `json:"currency"`
LastFour string `json:"last_four"` LastFour string `json:"last_four"`
ExpiryMonth int `json:"expiry_month"`
ExpiryYear int `json:"expiry_year"`
BankReturnCode string `json:"bank_return_code"` BankReturnCode string `json:"bank_return_code"`
TransactionStatusID int `json:"transaction_status_id"` TransactionStatusID int `json:"transaction_status_id"`
CreatedAt time.Time `json:"-"` CreatedAt time.Time `json:"-"`
@ -131,14 +133,17 @@ func (m *DBModel) InsertTransaction(txn Transaction) (int, error) {
defer cancel() defer cancel()
stmt := `INSERT INTO transactions stmt := `INSERT INTO transactions
(amount, currency, last_four, bank_return_code, (amount, currency, last_four, expiry_month, expiry_year,
bank_return_code,
transaction_status_id, created_at, updated_at) transaction_status_id, created_at, updated_at)
VALUES (?, ?, ?, ?, ?, ?, ?)` VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`
result, err := m.DB.ExecContext(ctx, stmt, result, err := m.DB.ExecContext(ctx, stmt,
txn.Amount, txn.Amount,
txn.Currency, txn.Currency,
txn.LastFour, txn.LastFour,
txn.ExpiryMonth,
txn.ExpiryYear,
txn.BankReturnCode, txn.BankReturnCode,
txn.TransactionStatusID, txn.TransactionStatusID,
time.Now(), time.Now(),
@ -160,17 +165,45 @@ func (m *DBModel) InsertOrder(order Order) (int, error) {
defer cancel() defer cancel()
stmt := `INSERT INTO orders stmt := `INSERT INTO orders
(widget_id, transaction_id, status_id, quantity, (widget_id, transaction_id, customer_id, status_id, quantity,
amount, created_at, updated_at) amount, created_at, updated_at)
VALUES (?, ?, ?, ?, ?, ?, ?)` VALUES (?, ?, ?, ?, ?, ?, ?, ?)`
result, err := m.DB.ExecContext(ctx, stmt, result, err := m.DB.ExecContext(ctx, stmt,
order.WidgetID, order.WidgetID,
order.TransactionID, order.TransactionID,
order.CustomerID,
order.StatusID, order.StatusID,
order.Quantity, order.Quantity,
order.Amount, order.Amount,
time.Now(), time.Now(),
time.Now(),
)
if err != nil {
return 0, err
}
id, err := result.LastInsertId()
if err != nil {
return 0, err
}
return int(id), nil
}
// InsertCustomer inserts a new customer, and returns its id
func (m *DBModel) InsertCustomer(customer Customer) (int, error) {
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
defer cancel()
stmt := `INSERT INTO customers
(first_name, last_name, email, created_at, updated_at)
VALUES (?, ?, ?, ?, ?)`
result, err := m.DB.ExecContext(ctx, stmt,
customer.FirstName,
customer.LastName,
customer.Email,
time.Now(),
time.Now(),
) )
if err != nil { if err != nil {
return 0, err return 0, err