Compare commits
No commits in common. "d32667acd168e7e28e0dddb73f1d4db5e5ac1b96" and "7e0df51d881d1fb92c2058a5efc6ed63b3ef4adc" have entirely different histories.
d32667acd1
...
7e0df51d88
2
Makefile
2
Makefile
@ -2,7 +2,7 @@ STRIPE_SECRET=$(shell sed '2q;d' cred.txt)
|
||||
STRIPE_KEY=$(shell sed '2q;d' cred.txt)
|
||||
GOSTRIPE_PORT=4000
|
||||
API_PORT=4001
|
||||
DSN=vinchent:secret@tcp(localhost:3306)/widgets?parseTime=true&tls=false
|
||||
DSN=root@tcp(localhost:6379)/widgets?parseTime=true&tls=false
|
||||
|
||||
## build: builds all binaries
|
||||
build: clean build_front build_back
|
||||
|
@ -5,7 +5,6 @@ import (
|
||||
"fmt"
|
||||
"log"
|
||||
"myapp/internal/driver"
|
||||
"myapp/internal/models"
|
||||
"net/http"
|
||||
"os"
|
||||
"time"
|
||||
@ -32,7 +31,6 @@ type application struct {
|
||||
infoLog *log.Logger
|
||||
errorLog *log.Logger
|
||||
version string
|
||||
DB models.DBModel
|
||||
}
|
||||
|
||||
func (app *application) serve() error {
|
||||
@ -89,7 +87,6 @@ func main() {
|
||||
config: cfg,
|
||||
infoLog: infoLog,
|
||||
errorLog: errorLog,
|
||||
DB: models.DBModel{DB: conn},
|
||||
}
|
||||
|
||||
app.infoLog.Println("Connected to MariaDB")
|
||||
|
@ -5,8 +5,6 @@ import (
|
||||
"myapp/internal/cards"
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
)
|
||||
|
||||
type stripePayload struct {
|
||||
@ -67,19 +65,3 @@ func (app *application) GetPaymentIntent(w http.ResponseWriter, r *http.Request)
|
||||
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)
|
||||
}
|
||||
|
@ -19,6 +19,5 @@ func (app *application) routes() http.Handler {
|
||||
}))
|
||||
|
||||
mux.Post("/api/payment-intent", app.GetPaymentIntent)
|
||||
mux.Get("/api/widget/{id}", app.GetWidgetByID)
|
||||
return mux
|
||||
}
|
||||
|
@ -1,12 +1,15 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"myapp/internal/models"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func (app *application) VirtualTerminal(w http.ResponseWriter, r *http.Request) {
|
||||
if err := app.renderTemplate(w, r, "terminal", &templateData{}, "stripe-js"); err != nil {
|
||||
stringMap := make(map[string]string)
|
||||
stringMap["publishable_key"] = app.config.stripe.key
|
||||
if err := app.renderTemplate(w, r, "terminal", &templateData{
|
||||
StringMap: stringMap,
|
||||
}, "stripe-js"); err != nil {
|
||||
app.errorLog.Println(err)
|
||||
}
|
||||
}
|
||||
@ -43,20 +46,7 @@ func (app *application) PaymentSucceeded(w http.ResponseWriter, r *http.Request)
|
||||
|
||||
// ChargeOnce displays the page to buy one widget
|
||||
func (app *application) ChargeOnce(w http.ResponseWriter, r *http.Request) {
|
||||
widget := models.Widget{
|
||||
ID: 1,
|
||||
Name: "Custom Widget",
|
||||
Description: "Paris 2024",
|
||||
InventoryLevel: 10,
|
||||
Price: 1000,
|
||||
}
|
||||
|
||||
data := make(map[string]interface{})
|
||||
data["widget"] = widget
|
||||
|
||||
if err := app.renderTemplate(w, r, "buy-once", &templateData{
|
||||
Data: data,
|
||||
}, "stripe-js"); err != nil {
|
||||
if err := app.renderTemplate(w, r, "buy-once", nil, "stripe-js"); err != nil {
|
||||
app.errorLog.Println(err)
|
||||
}
|
||||
}
|
||||
|
@ -6,7 +6,6 @@ import (
|
||||
"html/template"
|
||||
"log"
|
||||
"myapp/internal/driver"
|
||||
"myapp/internal/models"
|
||||
"net/http"
|
||||
"os"
|
||||
"time"
|
||||
@ -36,7 +35,6 @@ type application struct {
|
||||
errorLog *log.Logger
|
||||
templateCache map[string]*template.Template
|
||||
version string
|
||||
DB models.DBModel
|
||||
}
|
||||
|
||||
func (app *application) serve() error {
|
||||
@ -97,7 +95,6 @@ func main() {
|
||||
errorLog: errorLog,
|
||||
templateCache: tc,
|
||||
version: version,
|
||||
DB: models.DBModel{DB: conn},
|
||||
}
|
||||
|
||||
app.infoLog.Println("Connected to MariaDB")
|
||||
|
@ -20,26 +20,15 @@ type templateData struct {
|
||||
API string
|
||||
CSSVersion string
|
||||
IsAuthenticated int
|
||||
StripeSecretKey string
|
||||
StripePubKey string
|
||||
}
|
||||
|
||||
var functions = template.FuncMap{
|
||||
"formatCurrency": formatCurrency,
|
||||
}
|
||||
|
||||
func formatCurrency(n int) string {
|
||||
f := float32(n / 100)
|
||||
return fmt.Sprintf("€%.2f", f)
|
||||
}
|
||||
var functions = template.FuncMap{}
|
||||
|
||||
//go:embed templates
|
||||
var templateFS embed.FS
|
||||
|
||||
func (app *application) addDefaultData(td *templateData, r *http.Request) *templateData {
|
||||
td.API = app.config.api
|
||||
td.StripePubKey = app.config.stripe.key
|
||||
td.StripeSecretKey = app.config.stripe.secret
|
||||
return td
|
||||
}
|
||||
|
||||
|
@ -3,7 +3,6 @@
|
||||
Buy one widget
|
||||
{{ end }}
|
||||
{{ define "content" }}
|
||||
{{$widget := index .Data "widget"}}
|
||||
<h2 class="mt-3 text-center">Buy One Widget</h2>
|
||||
<hr>
|
||||
<img src="/static/img/widget.jpeg"
|
||||
@ -18,12 +17,15 @@ Buy one widget
|
||||
class="d-blick needs-validation charge-form"
|
||||
autocomplete="off"
|
||||
novalidate="">
|
||||
<input type="hidden" name="product_id" value="{{$widget.ID}}">
|
||||
<input type="hidden" id="amount" name="amount" value="{{$widget.Price}}">
|
||||
<h3 class="mt-2 mb-3 text-center">{{$widget.Name}}: {{formatCurrency $widget.Price}}</h3>
|
||||
<p class="mt-2 mb-3 text-center">{{$widget.Description}}</p>
|
||||
<hr>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="amount" class="form-label">Amount</label>
|
||||
<input type="text"
|
||||
id="amount"
|
||||
name="amount"
|
||||
autocomplete="amount-new"
|
||||
required=""
|
||||
class="form-control">
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="cardholder-name" class="form-label">Cardholder Name</label>
|
||||
<input type="text"
|
||||
|
@ -8,7 +8,7 @@ const cardMessages = document.getElementById("card-messages");
|
||||
const payButton = document.getElementById("pay-button");
|
||||
const processing = document.getElementById("processing-payment");
|
||||
|
||||
stripe = Stripe('{{.StripePubKey}}');
|
||||
stripe = Stripe('{{index .StringMap "publishable_key"}}');
|
||||
|
||||
function hidePayButton() {
|
||||
payButton.classList.add("d-none");
|
||||
@ -46,7 +46,7 @@ function val() {
|
||||
form.classList.add("was-validated");
|
||||
hidePayButton();
|
||||
|
||||
let amountToCharge = String(parseFloat(document.getElementById("amount").value));
|
||||
let amountToCharge = String(parseFloat(document.getElementById("amount").value) * 100);
|
||||
let payload = {
|
||||
amount: amountToCharge,
|
||||
currency: 'eur',
|
||||
@ -61,7 +61,7 @@ function val() {
|
||||
body: JSON.stringify(payload),
|
||||
};
|
||||
|
||||
fetch("{{.API}}/api/payment-intent", requestOptions)
|
||||
fetch("{{index .API}}/api/payment-intent", requestOptions)
|
||||
.then(response => response.text())
|
||||
.then(response => {
|
||||
let data;
|
||||
|
@ -1,103 +0,0 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"time"
|
||||
)
|
||||
|
||||
// DBModel is the type for database connection values
|
||||
type DBModel struct {
|
||||
DB *sql.DB
|
||||
}
|
||||
|
||||
// Models is the wrapper for all models
|
||||
type Models struct {
|
||||
DB DBModel
|
||||
}
|
||||
|
||||
// NewModels returns a model type with database connection pool
|
||||
func NewModels(db *sql.DB) Models {
|
||||
return Models{
|
||||
DB: DBModel{DB: db},
|
||||
}
|
||||
}
|
||||
|
||||
// Widget is the type for all widgets
|
||||
type Widget struct {
|
||||
ID int `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
InventoryLevel int `json:"inventory_level"`
|
||||
Price int `json:"price"`
|
||||
CreatedAt time.Time `json:"-"`
|
||||
UpdatedAt time.Time `json:"-"`
|
||||
Image string `json:"image"`
|
||||
}
|
||||
|
||||
// Order is the type for all orders
|
||||
type Order struct {
|
||||
ID int `json:"id"`
|
||||
WidgetID int `json:"widget_id"`
|
||||
TransactionID int `json:"transaction_id"`
|
||||
StatusID int `json:"status_id"`
|
||||
Quantity int `json:"quantity"`
|
||||
Amount int `json:"amount"`
|
||||
CreatedAt time.Time `json:"-"`
|
||||
UpdatedAt time.Time `json:"-"`
|
||||
}
|
||||
|
||||
// Status is the type for orders statuses
|
||||
type Status struct {
|
||||
ID int `json:"id"`
|
||||
Name string `json:"name"`
|
||||
CreatedAt time.Time `json:"-"`
|
||||
UpdatedAt time.Time `json:"-"`
|
||||
}
|
||||
|
||||
// TransactionStatus is the type for transaction statuses
|
||||
type TransactionStatus struct {
|
||||
ID int `json:"id"`
|
||||
Name string `json:"name"`
|
||||
CreatedAt time.Time `json:"-"`
|
||||
UpdatedAt time.Time `json:"-"`
|
||||
}
|
||||
|
||||
// Transaction is the type for transactions
|
||||
type Transaction struct {
|
||||
ID int `json:"id"`
|
||||
Amount int `json:"amount"`
|
||||
Currency string `json:"currency"`
|
||||
LastFour string `json:"last_four"`
|
||||
BankReturnCode string `json:bank_return_code`
|
||||
TransactionStatusID int `json:transaction_status_id`
|
||||
CreatedAt time.Time `json:"-"`
|
||||
UpdatedAt time.Time `json:"-"`
|
||||
}
|
||||
|
||||
// User is the type for users
|
||||
type User struct {
|
||||
ID int `json:"id"`
|
||||
FirstName string `json:"first_name"`
|
||||
LastName string `json:"last_name"`
|
||||
Email string `json:"email"`
|
||||
Password string `json:"password"`
|
||||
CreatedAt time.Time `json:"-"`
|
||||
UpdatedAt time.Time `json:"-"`
|
||||
}
|
||||
|
||||
func (m *DBModel) GetWidget(id int) (Widget, error) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
|
||||
defer cancel()
|
||||
|
||||
var widget Widget
|
||||
|
||||
query := `SELECT id, name FROM widgets WHERE id = ?`
|
||||
|
||||
row := m.DB.QueryRowContext(ctx, query, id)
|
||||
err := row.Scan(&widget.ID, &widget.Name)
|
||||
if err != nil {
|
||||
return widget, err
|
||||
}
|
||||
return widget, nil
|
||||
}
|
BIN
migrations/migrations/.DS_Store
vendored
BIN
migrations/migrations/.DS_Store
vendored
Binary file not shown.
@ -1,12 +0,0 @@
|
||||
create_table("widgets") {
|
||||
t.Column("id", "integer", {primary: true})
|
||||
t.Column("name", "string", {"default": ""})
|
||||
t.Column("description", "text", {"default": ""})
|
||||
t.Column("inventory_level", "integer", {})
|
||||
t.Column("price", "integer", {})
|
||||
}
|
||||
|
||||
sql("alter table widgets alter column created_at set default now();")
|
||||
sql("alter table widgets alter column updated_at set default now();")
|
||||
|
||||
sql("insert into widgets (name, description, inventory_level, price, created_at, updated_at) values ('Widget', 'A very nice widget.', 10, 1000, now(), now());")
|
@ -1,13 +0,0 @@
|
||||
create_table("transaction_statuses") {
|
||||
t.Column("id", "integer", {primary: true})
|
||||
t.Column("name", "string", {})
|
||||
}
|
||||
|
||||
sql("alter table transaction_statuses alter column created_at set default now();")
|
||||
sql("alter table transaction_statuses alter column updated_at set default now();")
|
||||
|
||||
sql("insert into transaction_statuses (name) values ('Pending');")
|
||||
sql("insert into transaction_statuses (name) values ('Cleared');")
|
||||
sql("insert into transaction_statuses (name) values ('Declined');")
|
||||
sql("insert into transaction_statuses (name) values ('Refunded');")
|
||||
sql("insert into transaction_statuses (name) values ('Partially refunded');")
|
@ -1 +0,0 @@
|
||||
drop_table("transactions")
|
@ -1,16 +0,0 @@
|
||||
create_table("transactions") {
|
||||
t.Column("id", "integer", {primary: true})
|
||||
t.Column("amount", "integer", {})
|
||||
t.Column("currency", "string", {})
|
||||
t.Column("last_four", "string", {})
|
||||
t.Column("bank_return_code", "string", {})
|
||||
t.Column("transaction_status_id", "integer", {"unsigned": true})
|
||||
}
|
||||
|
||||
sql("alter table transactions alter column created_at set default now();")
|
||||
sql("alter table transactions alter column updated_at set default now();")
|
||||
|
||||
add_foreign_key("transactions", "transaction_status_id", {"transaction_statuses": ["id"]}, {
|
||||
"on_delete": "cascade",
|
||||
"on_update": "cascade",
|
||||
})
|
@ -1 +0,0 @@
|
||||
drop_table("orders")
|
@ -1,21 +0,0 @@
|
||||
create_table("orders") {
|
||||
t.Column("id", "integer", {primary: true})
|
||||
t.Column("widget_id", "integer", {"unsigned":true})
|
||||
t.Column("transaction_id", "integer", {"unsigned":true})
|
||||
t.Column("status_id", "integer", {"unsigned":true})
|
||||
t.Column("quantity", "integer", {})
|
||||
t.Column("amount", "integer", {})
|
||||
}
|
||||
|
||||
sql("alter table orders alter column created_at set default now();")
|
||||
sql("alter table orders alter column updated_at set default now();")
|
||||
|
||||
add_foreign_key("orders", "widget_id", {"widgets": ["id"]}, {
|
||||
"on_delete": "cascade",
|
||||
"on_update": "cascade",
|
||||
})
|
||||
|
||||
add_foreign_key("orders", "transaction_id", {"transactions": ["id"]}, {
|
||||
"on_delete": "cascade",
|
||||
"on_update": "cascade",
|
||||
})
|
@ -1 +0,0 @@
|
||||
drop_table("statuses")
|
@ -1,16 +0,0 @@
|
||||
create_table("statuses") {
|
||||
t.Column("id", "integer", {primary: true})
|
||||
t.Column("name", "string", {})
|
||||
}
|
||||
|
||||
sql("alter table statuses alter column created_at set default now();")
|
||||
sql("alter table statuses alter column updated_at set default now();")
|
||||
|
||||
sql("insert into statuses (name) values ('Cleared');")
|
||||
sql("insert into statuses (name) values ('Refunded');")
|
||||
sql("insert into statuses (name) values ('Cancelled');")
|
||||
|
||||
add_foreign_key("orders", "status_id", {"statuses": ["id"]}, {
|
||||
"on_delete": "cascade",
|
||||
"on_update": "cascade",
|
||||
})
|
@ -1 +0,0 @@
|
||||
drop_table("users")
|
@ -1,12 +0,0 @@
|
||||
create_table("users") {
|
||||
t.Column("id", "integer", {primary: true})
|
||||
t.Column("first_name", "string", {"size": 255})
|
||||
t.Column("last_name", "string", {"size": 255})
|
||||
t.Column("email", "string", {})
|
||||
t.Column("password", "string", {"size": 60})
|
||||
}
|
||||
|
||||
sql("alter table users alter column created_at set default now();")
|
||||
sql("alter table users alter column updated_at set default now();")
|
||||
|
||||
sql("insert into users (first_name, last_name, email, password) values ('Admin','User','admin@example.com', '$2a$12$VR1wDmweaF3ZTVgEHiJrNOSi8VcS4j0eamr96A/7iOe8vlum3O3/q');")
|
@ -1 +0,0 @@
|
||||
drop_column("widgets", "image")
|
@ -1 +0,0 @@
|
||||
add_column("widgets", "image", "string", {"default":""})
|
@ -4,7 +4,8 @@ const cardMessages = document.getElementById("card-messages");
|
||||
const payButton = document.getElementById("pay-button");
|
||||
const processing = document.getElementById("processing-payment");
|
||||
|
||||
stripe = Stripe('{{.StripePubKey}}');
|
||||
// FIXME: not working in this way
|
||||
stripe = Stripe('{{index .StringMap "publishable_key"}}');
|
||||
|
||||
function hidePayButton() {
|
||||
payButton.classList.add("d-none");
|
||||
@ -30,7 +31,7 @@ function showCardSuccess() {
|
||||
cardMessages.innerText = "Trasaction successful";
|
||||
}
|
||||
|
||||
function val(stripe) {
|
||||
function val() {
|
||||
let form = document.getElementById("charge_form");
|
||||
|
||||
if (form.checkValidity() === false) {
|
||||
@ -42,7 +43,7 @@ function val(stripe) {
|
||||
form.classList.add("was-validated");
|
||||
hidePayButton();
|
||||
|
||||
let amountToCharge = String(parseFloat(document.getElementById("amount").value));
|
||||
let amountToCharge = String(parseFloat(document.getElementById("amount").value) * 100);
|
||||
let payload = {
|
||||
amount: amountToCharge,
|
||||
currency: 'eur',
|
||||
@ -57,7 +58,7 @@ function val(stripe) {
|
||||
body: JSON.stringify(payload),
|
||||
};
|
||||
|
||||
fetch("{{.API}}/api/payment-intent", requestOptions)
|
||||
fetch("{{index .API}}/api/payment-intent", requestOptions)
|
||||
.then(response => response.text())
|
||||
.then(response => {
|
||||
let data;
|
||||
@ -127,3 +128,4 @@ function val(stripe) {
|
||||
}
|
||||
});
|
||||
})();
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user