server, still need to correct public

This commit is contained in:
tavo-wasd 2024-08-31 15:56:52 -06:00
parent e9e0429e7c
commit 463476bdc8
6 changed files with 603 additions and 495 deletions

View file

@ -1,6 +1,15 @@
BASE_URL=""
BASE_URL="https://api-m.sandbox.paypal.com"
PORT="8080"
CLIENT_ID=""
CLIENT_SECRET=""
PLAN_ID=""
RETURN_URL="https://builder.conex.one/?success"
CANCEL_URL="https://builder.conex.one/?cancel"
RETURN_URL="http://localhost:8080/?success"
CANCEL_URL="http://localhost:8080/?cancel"
DB_HOST="localhost"
DB_PORT="5432"
DB_USER="conex"
DB_PASS="1234"
DB_NAME="iterone"
PRICE="20.00"

4
.gitignore vendored
View file

@ -1,6 +1,2 @@
builder
bin
go.mod
go.sum
public/submit/*
.env

View file

@ -1,16 +1,17 @@
BIN = builder
SRC = main.go
GOFILES = go.sum go.mod
SRCDIR = server
SRC = ${SRCDIR}/main.go
GOFILES = ${SRCDIR}/go.sum ${SRCDIR}/go.mod
GOMODS = github.com/joho/godotenv github.com/lib/pq
all: ${BIN}
${BIN}: ${SRC} ${GOFILES}
go build -o builder
(cd ${SRCDIR} && go build -o ../${BIN})
${GOFILES}:
go mod init ${BIN}
go get ${GOMODS}
(cd ${SRCDIR} && go mod init ${BIN})
(cd ${SRCDIR} && go get ${GOMODS})
start: ${BIN}
@./$< &
@ -26,4 +27,4 @@ clean:
rm -f ${BIN}
clean-mods:
rm -f go.*
rm -f ${SRCDIR}/go.*

195
README.org Normal file
View file

@ -0,0 +1,195 @@
#+TITLE: Conex Builder Documentation
#+PROPERTY: header-args:sql :engine postgres :dbhost "localhost" :dbport 5432 :dbuser "conex" :dbpassword "1234" :database "iterone"
* Database
** Create DB
#+begin_src sh
sudo su - postgres
psql
#+end_src
Then:
#+BEGIN_SRC sql
CREATE DATABASE iterone OWNER conex;
#+END_SRC
** Sites table
#+BEGIN_SRC sql :results silent
DROP TABLE IF EXISTS changes;
DROP TABLE IF EXISTS payments;
DROP TABLE IF EXISTS sites;
CREATE TABLE sites (
id SERIAL PRIMARY KEY,
folder VARCHAR(35) UNIQUE NOT NULL,
status VARCHAR(4),
due TIMESTAMPTZ NOT NULL,
name VARCHAR(50),
sur VARCHAR(50),
email VARCHAR(100) NOT NULL,
phone VARCHAR(20),
code VARCHAR(2)
);
#+END_SRC
#+BEGIN_SRC sql :results silent
INSERT INTO sites (folder, status, due, name, sur, email, phone, code)
VALUES ('athos', 'up', '2025-08-31T20:26:58Z', 'John', 'Doe', 'john@doe', '8888-8888', 'CR');
#+END_SRC
#+BEGIN_SRC sql
SELECT * FROM sites;
#+END_SRC
#+RESULTS:
| id | folder | status | due | name | sur | email | phone | code |
|----+-----------+--------+------------------------+------+-----+---------------------------------------+------------+------|
| 1 | athos | up | 2026-08-31 15:27:00-06 | John | Doe | sb-8kx8c32267916@personal.example.com | 5068031951 | CR |
| 2 | gofitness | up | 2025-08-31 15:29:01-06 | John | Doe | sb-8kx8c32267916@personal.example.com | 5068031951 | CR |
** Payments table
#+BEGIN_SRC sql :results silent
DROP TABLE IF EXISTS changes;
DROP TABLE IF EXISTS payments;
CREATE TABLE payments (
id SERIAL PRIMARY KEY,
capture VARCHAR(100) NOT NULL,
site INTEGER REFERENCES sites(id) NOT NULL,
amount DECIMAL(10, 2) NOT NULL,
currency VARCHAR(3) NOT NULL,
status VARCHAR(18) NOT NULL, -- PayPal capture status length -- https://developer.paypal.com/docs/api/orders/v2/#orders_capture
date DATE NOT NULL
);
#+END_SRC
#+BEGIN_SRC sql :results silent
INSERT INTO payments (capture, site, amount, currency, date, status)
VALUES ('5PS47268T4115691X', 1, 20.00, 'USD', '2024-08-30', 'COMPLETED');
#+END_SRC
#+BEGIN_SRC sql
SELECT * FROM payments;
#+END_SRC
#+RESULTS:
| id | capture | site | amount | currency | status | date |
|----+-------------------+------+--------+----------+-----------+------------|
| 1 | 6H6838025H7236834 | 1 | 20.00 | USD | COMPLETED | 2024-08-31 |
| 2 | 48H30563GU472432N | 1 | 20.00 | USD | COMPLETED | 2024-08-31 |
| 3 | 3UD50608FD4050042 | 2 | 20.00 | USD | COMPLETED | 2024-08-31 |
** Changes table
#+BEGIN_SRC sql :results silent
DROP TABLE IF EXISTS changes;
CREATE TABLE changes (
id INTEGER PRIMARY KEY,
by VARCHAR(20) NOT NULL,
site INTEGER REFERENCES sites(id),
payment INTEGER REFERENCES payments(id),
col VARCHAR(6) NOT NULL,
prev VARCHAR(8) NOT NULL,
next VARCHAR(8) NOT NULL,
date DATE NOT NULL
);
#+END_SRC
#+BEGIN_SRC sql
SELECT * FROM changes;
#+END_SRC
#+RESULTS:
| id | by | site | payment | col | prev | next | date |
|----+----+------+---------+-----+------+------+------|
** Types of changes
*** Payments
- status: complete/refunded/modified
- amount: prev/next
*** Sites
- folder: prev/next
- status: up/down
- due: date/date
- name: prev/next
- sur: prev/next
- email: prev/next
- phone: prev/next
- code: prev/next
* Error codes
** http.Error
** Fatalf
Fatal error will cause program shutdown by calling ~os.Exit(1)~.
*** Error 000: Missing credentials
*Package*: ~main~
*Function*: ~init()~
*Libraries*: ~os~, ~log~, ~github.com/joho/godotenv~
Authentication and other parameters are located in the ~.env~ file which mist be
located at the root of main binary execution.
Possible causes for error are:
- Binary execution directory doesn't have the ~.env~ file
- Missing parameters for initializing environment values
- Corruption of ~.env~ file
- Library error
Steps to troubleshoot:
1. Check ~.env~ exists
2. Check ~.env~ authentication values
3. Check ~.env~ file integrity
4. Update, rollback or troubleshoot library
*** Error 001: Can't connect to database
*Package*: ~main~
*Function*: ~init()~
*Libraries*: ~os~, ~log~
The ~db~ object manages database queries. This object is used to ping the
database, a correct ping depends on correctly set credentials, and properly
initialized ~db~ object.
Possible causes for error are:
- Wrong database credentials
- Missing database credentials
Steps to troubleshoot:
1. Check set, correct and valid credentials in ~.env~ file
*** Error: 002: Can't start server
*Package*: ~main~
*Function*: ~main()~
*Libraries*: ~os~, ~log~, ~net/http~, ~os/signal~
The server runs in a Goroutine, started on a port defined in ~.env~.
Possible causes for error are:
- Port is in use
- Port usage denied
Steps to troubleshoot:
1. Check set, correct and valid port in ~.env~ file

481
main.go
View file

@ -1,481 +0,0 @@
package main
import (
"bytes"
"database/sql"
"encoding/json"
"fmt"
"io/ioutil"
"log"
"net/http"
"os"
"os/signal"
"strings"
"syscall"
"time"
"github.com/joho/godotenv"
_ "github.com/lib/pq"
)
var (
baseURL string
clientID string
clientSecret string
planID string
returnUrl string
cancelUrl string
exists bool
err error
dbHost string
dbPort string
dbUser string
dbPass string
dbName string
db *sql.DB
query string
)
func init() {
// Load .env
if err := godotenv.Load(); err != nil {
log.Fatalf("Error loading .env file: %v", err)
}
// Set variables
baseURL = os.Getenv("BASE_URL")
clientID = os.Getenv("CLIENT_ID")
clientSecret = os.Getenv("CLIENT_SECRET")
planID = os.Getenv("PLAN_ID")
returnUrl = os.Getenv("RETURN_URL")
cancelUrl = os.Getenv("CANCEL_URL")
// DB creds
dbHost = os.Getenv("DB_HOST")
dbPort = os.Getenv("DB_PORT")
dbUser = os.Getenv("DB_USER")
dbPass = os.Getenv("DB_PASS")
dbName = os.Getenv("DB_NAME")
// Error if empty
if baseURL == "" || clientID == "" || clientSecret == "" || planID == "" || returnUrl == "" || cancelUrl == "" ||
dbHost == "" || dbPort == "" || dbUser == "" || dbPass == "" || dbName == "" {
log.Fatalf("Error setting credentials")
}
// Connect to DB
var err error
connStr := fmt.Sprintf("host=%s port=%s user=%s password=%s dbname=%s sslmode=disable",
dbHost, dbPort, dbUser, dbPass, dbName)
db, err = sql.Open("postgres", connStr)
if err != nil {
log.Fatalf("Failed to connect to database: %v", err)
}
// Ping DB
if err = db.Ping(); err != nil {
log.Fatalf("Failed to ping database: %v", err)
}
}
type CreateOrderResponse struct {
ID string `json:"id"`
}
type OrderResponse struct {
ID string `json:"id"`
Status string `json:"status"`
PurchaseUnits []struct {
Payments struct {
Captures []struct {
ID string `json:"id"`
Status string `json:"status"`
CreateTime time.Time `json:"create_time"`
} `json:"captures"`
} `json:"payments"`
} `json:"purchase_units"`
Payer struct {
Name struct {
GivenName string `json:"given_name"`
Surname string `json:"surname"`
} `json:"name"`
EmailAddress string `json:"email_address"`
Phone struct {
PhoneType string `json:"phone_type"`
PhoneNumber struct {
NationalNumber string `json:"national_number"`
} `json:"phone_number"`
} `json:"phone"`
Address struct {
CountryCode string `json:"country_code"`
} `json:"address"`
} `json:"payer"`
}
type SubscriptionResponse struct {
Status string `json:"status"`
StatusUpdateTime time.Time `json:"status_update_time"`
StartTime time.Time `json:"start_time"`
Subscriber struct {
Name struct {
GivenName string `json:"given_name"`
Surname string `json:"surname"`
} `json:"name"`
EmailAddress string `json:"email_address"`
} `json:"subscriber"`
CreateTime time.Time `json:"create_time"`
}
type Cart struct {
Directory string `json:"directory"`
}
func main() {
http.HandleFunc("/api/order", CreateOrder)
http.HandleFunc("/api/order/", CaptureOrder)
http.HandleFunc("/api/paypal/subscribe", CreateSubscription)
http.HandleFunc("/api/paypal/subscribe/", CaptureSubscription)
http.Handle("/", http.FileServer(http.Dir("./public")))
// Channel to listen for signals
stop := make(chan os.Signal, 1)
signal.Notify(stop, syscall.SIGINT, syscall.SIGTERM)
// Run the server in a goroutine so that it doesn't block
go func() {
log.Println("Starting server on :8080...")
if err := http.ListenAndServe(":8080", nil); err != nil {
log.Fatalf("Could not listen on :8080: %v\n", err)
}
}()
<-stop // Shutdown signal recieved
log.Println("Server shutdown gracefully.")
}
func Token() (string, error) {
// Create
req, err := http.NewRequest("POST", baseURL+"/v1/oauth2/token", strings.NewReader(`grant_type=client_credentials`))
if err != nil {
return "", fmt.Errorf("Error creating request: %v", err)
}
// Send
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
req.SetBasicAuth(os.Getenv("CLIENT_ID"), os.Getenv("CLIENT_SECRET"))
resp, err := http.DefaultClient.Do(req)
if err != nil {
return "", fmt.Errorf("Error sending request: %v", err)
}
defer resp.Body.Close()
// Decode
var result struct {
AccessToken string `json:"access_token"`
}
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
return "", fmt.Errorf("Error decoding response: %v", err)
}
// Return
return result.AccessToken, nil
}
func RegisterOrder(order OrderResponse, directory string) {
var (
capture string
status string
name string
surname string
email string
phone string
country string
date time.Time
)
for _, Unit := range order.PurchaseUnits {
for _, Capture := range Unit.Payments.Captures {
capture = Capture.ID
status = Capture.Status
date = Capture.CreateTime
}
}
name = order.Payer.Name.GivenName
surname = order.Payer.Name.Surname
email = order.Payer.EmailAddress
phone = order.Payer.Phone.PhoneNumber.NationalNumber
country = order.Payer.Address.CountryCode
// Register Payment
_, err = db.Exec(`INSERT INTO payments (id, client, directory, status, step, date) VALUES ($1, $2, $3, $4, $5, $6);`,
capture, email, directory, status, "REGISTERED", date)
if err != nil {
fmt.Printf("$v", err) // TODO consider logging in server
}
// Register Client
err = db.QueryRow(`SELECT EXISTS(SELECT 1 FROM clients WHERE email = $1);`, email).Scan(&exists)
if err != nil {
fmt.Printf("$v", err) // TODO consider logging in server
}
if !exists {
_, err = db.Exec(`INSERT INTO clients (email, name, surname, phone, country) VALUES ($1, $2, $3, $4, $5);`,
email, name, surname, phone, country)
if err != nil {
fmt.Printf("$v", err) // TODO consider logging in server
}
}
// Register Site
_, err = db.Exec(`INSERT INTO sites (directory, client, status, ends) VALUES ($1, $2, $3, $4);`,
directory, email, "ACTIVE", date.AddDate(1, 0, 0)) // Ends a year later
if err != nil {
fmt.Printf("$v", err) // TODO consider logging in server
}
}
func CreateOrder(w http.ResponseWriter, r *http.Request) {
token, err := Token()
if err != nil {
http.Error(w, "Failed to get access token", http.StatusInternalServerError)
return
}
data := `{
"intent": "CAPTURE",
"purchase_units": [{
"amount": {
"currency_code": "USD",
"value": "20.00"
}
}],
"payment_source": {
"paypal": {
"address" : {
"country_code": "CR"
}
}
},
"application_context": {
"shipping_preference": "NO_SHIPPING"
}
}`
// Create
req, err := http.NewRequest("POST", baseURL+"/v2/checkout/orders", bytes.NewBuffer([]byte(data)))
if err != nil {
http.Error(w, "Failed to create request", http.StatusInternalServerError)
return
}
// Send
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Authorization", "Bearer "+token)
resp, err := http.DefaultClient.Do(req)
if err != nil {
http.Error(w, "Failed to send request", http.StatusInternalServerError)
return
}
defer resp.Body.Close()
// Decode
var result CreateOrderResponse
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
http.Error(w, "Failed to decode response", http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(map[string]string{"id": result.ID})
return
}
func CaptureOrder(w http.ResponseWriter, r *http.Request) {
// Read body from order
body, err := ioutil.ReadAll(r.Body)
if err != nil {
http.Error(w, "Failed to read request body", http.StatusInternalServerError)
return
}
// Parse to get directory
var cart Cart
err = json.Unmarshal(body, &cart)
if err != nil {
http.Error(w, "Failed to parse request body", http.StatusBadRequest)
return
}
directory := cart.Directory
// Get orderID
path := strings.TrimPrefix(r.URL.Path, "/api/orders/")
parts := strings.Split(path, "/")
orderID := parts[0]
if orderID == "" {
http.Error(w, "Failed to get orderID from client URL", http.StatusInternalServerError)
return
}
token, err := Token()
if err != nil {
http.Error(w, "Failed to get access token", http.StatusInternalServerError)
return
}
// Create
req, err := http.NewRequest("POST", baseURL+"/v2/checkout/orders/"+orderID+"/capture", nil)
if err != nil {
http.Error(w, "Failed to create request", http.StatusInternalServerError)
return
}
// Check if directory already exists
err = db.QueryRow("SELECT EXISTS (SELECT 1 FROM sites WHERE directory = $1 LIMIT 1);", directory).Scan(&exists)
if err != nil {
http.Error(w, "Failed to check directory ID against database", http.StatusBadRequest)
return
}
if exists {
http.Error(w, "This directory ID is already taken", http.StatusBadRequest)
return
}
// Send, PAYMENT MADE HERE
req.Header.Set("Authorization", "Bearer "+token)
req.Header.Set("Content-Type", "application/json")
resp, err := http.DefaultClient.Do(req)
if err != nil {
http.Error(w, "Failed to send request", http.StatusInternalServerError)
return
}
defer resp.Body.Close()
// Decode
var order OrderResponse
if err := json.NewDecoder(resp.Body).Decode(&order); err != nil {
http.Error(w, "Failed to decode response", http.StatusInternalServerError)
return
}
RegisterOrder(order, directory)
// Respond
w.Header().Set("Content-Type", "application/json")
if err := json.NewEncoder(w).Encode(order); err != nil {
http.Error(w, "Failed to encode response", http.StatusInternalServerError)
}
}
func CreateSubscription(w http.ResponseWriter, r *http.Request) {
// Read body from order
body, err := ioutil.ReadAll(r.Body)
if err != nil {
http.Error(w, "Failed to read request body", http.StatusInternalServerError)
return
}
// Parse to get directory
var cart Cart
err = json.Unmarshal(body, &cart)
if err != nil {
http.Error(w, "Failed to parse request body", http.StatusBadRequest)
return
}
directory := cart.Directory
token, err := Token()
if err != nil {
http.Error(w, "Failed to get access token", http.StatusInternalServerError)
return
}
payload := map[string]interface{}{
"plan_id": planID,
"application_context": map[string]string{
"shipping_preference": "NO_SHIPPING",
"return_url": returnUrl,
"cancel_url": cancelUrl,
},
}
jsonData, err := json.Marshal(payload)
if err != nil {
http.Error(w, "Server error", http.StatusInternalServerError)
return
}
// Create request
log.Printf("Creating request")
req, err := http.NewRequest("POST", baseURL+"/v1/billing/subscriptions", bytes.NewBuffer(jsonData))
if err != nil {
http.Error(w, "Failed to create request", http.StatusInternalServerError)
return
}
// Check if directory already exists
err = db.QueryRow("SELECT EXISTS (SELECT 1 FROM sites WHERE directory = $1 LIMIT 1);", directory).Scan(&exists)
if err != nil {
http.Error(w, "Failed to check directory ID against database", http.StatusBadRequest)
return
}
if exists {
http.Error(w, "This directory ID is already taken", http.StatusBadRequest)
return
}
// Send
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Authorization", "Bearer "+token)
req.Header.Set("Accept", "application/json")
req.Header.Set("Prefer", "return=representation")
resp, err := http.DefaultClient.Do(req)
if err != nil {
http.Error(w, "Failed to send request", http.StatusInternalServerError)
return
}
defer resp.Body.Close()
// Decode
var result map[string]interface{}
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
http.Error(w, "Failed to decode response", http.StatusInternalServerError)
return
}
// Respond
w.Header().Set("Content-Type", "application/json")
if err := json.NewEncoder(w).Encode(result); err != nil {
http.Error(w, "Failed to encode response", http.StatusInternalServerError)
}
log.Printf("sent response to client")
}
// Capture just like CaptureOrder, but with response from paypal
// https://developer.paypal.com/docs/api/subscriptions/v1/#subscriptions_get
func CaptureSubscription(w http.ResponseWriter, r *http.Request) {
// Read body from order
body, err := ioutil.ReadAll(r.Body)
if err != nil {
http.Error(w, "Failed to read request body", http.StatusInternalServerError)
return
}
// Parse to get directory
var cart Cart
err = json.Unmarshal(body, &cart)
if err != nil {
http.Error(w, "Failed to parse request body", http.StatusBadRequest)
return
}
// directory := cart.Directory
// Get subID
path := strings.TrimPrefix(r.URL.Path, "/api/subscribe/")
parts := strings.Split(path, "/")
subID := parts[0]
if subID == "" {
http.Error(w, "Failed to get subID from client URL", http.StatusInternalServerError)
return
}
}

388
server/main.go Normal file
View file

@ -0,0 +1,388 @@
package main
import (
"bytes"
"database/sql"
"encoding/json"
"fmt"
"io"
"log"
"net/http"
"os"
"os/signal"
"strings"
"syscall"
"time"
"github.com/joho/godotenv"
_ "github.com/lib/pq"
)
var db *sql.DB
type Capture struct {
ID string `json:"id"`
Status string `json:"status"`
PurchaseUnits []struct {
Payments struct {
Captures []struct {
ID string `json:"id"`
Status string `json:"status"`
Amount struct {
CurrencyCode string `json:"currency_code"`
Value string `json:"value"`
} `json:"amount"`
CreateTime time.Time `json:"create_time"`
} `json:"captures"`
} `json:"payments"`
} `json:"purchase_units"`
Payer struct {
Name struct {
GivenName string `json:"given_name"`
Surname string `json:"surname"`
} `json:"name"`
EmailAddress string `json:"email_address"`
Phone struct {
PhoneType string `json:"phone_type"`
PhoneNumber struct {
NationalNumber string `json:"national_number"`
} `json:"phone_number"`
} `json:"phone"`
Address struct {
CountryCode string `json:"country_code"`
} `json:"address"`
} `json:"payer"`
}
func init() {
godotenv.Load()
if os.Getenv("BASE_URL") == "" ||
os.Getenv("CLIENT_ID") == "" ||
os.Getenv("CLIENT_SECRET") == "" ||
os.Getenv("RETURN_URL") == "" ||
os.Getenv("CANCEL_URL") == "" ||
os.Getenv("PORT") == "" {
log.Fatalf("Error 000: Missing credentials")
}
var err error
db, err = sql.Open("postgres", "host="+os.Getenv("DB_HOST")+
" port="+os.Getenv("DB_PORT")+
" user="+os.Getenv("DB_USER")+
" password="+os.Getenv("DB_PASS")+
" dbname="+os.Getenv("DB_NAME"))
if err != nil {
log.Fatalf("Error 001: Can't connect to database: %v", err)
}
if err := db.Ping(); err != nil {
log.Fatalf("Error 001: Can't connect to database: %v", err)
}
log.Println("Established database connection")
}
func main() {
http.HandleFunc("/api/orders", CreateOrder)
http.HandleFunc("/api/orders/", CaptureOrder)
http.Handle("/", http.FileServer(http.Dir("./public")))
stop := make(chan os.Signal, 1)
signal.Notify(stop, syscall.SIGINT, syscall.SIGTERM)
port := os.Getenv("PORT")
go func() {
log.Println("Starting server on " + port + "...")
if err := http.ListenAndServe(":"+port, nil); err != nil {
log.Fatalf("Error: 002: Can't start server: %v\n", err)
}
}()
<-stop
defer func() {
if db != nil {
if err := db.Close(); err != nil {
log.Fatalf("Error: Can't close database connection: %v", err)
}
}
}()
log.Println("Server shutdown gracefully.")
}
func Token() (string, error) {
req, err := http.NewRequest("POST",
os.Getenv("BASE_URL")+"/v1/oauth2/token",
strings.NewReader(`grant_type=client_credentials`))
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
req.SetBasicAuth(os.Getenv("CLIENT_ID"), os.Getenv("CLIENT_SECRET"))
raw, err := http.DefaultClient.Do(req)
if err != nil {
return "", fmt.Errorf("Error sending request: %v", err)
}
defer raw.Body.Close()
var response struct {
AccessToken string `json:"access_token"`
}
if err := json.NewDecoder(raw.Body).Decode(&response); err != nil {
return "", fmt.Errorf("Error decoding response: %v", err)
}
return response.AccessToken, nil
}
func RegisterOrder(capture Capture, directory string) {
var (
// Payment
id string
amount string
currency string
pstatus string
date time.Time
// Website
wstatus string
due time.Time
name string
surname string
email string
phone string
country string
)
id = capture.PurchaseUnits[0].Payments.Captures[0].ID
amount = capture.PurchaseUnits[0].Payments.Captures[0].Amount.Value
currency = capture.PurchaseUnits[0].Payments.Captures[0].Amount.CurrencyCode
pstatus = capture.PurchaseUnits[0].Payments.Captures[0].Status
date = capture.PurchaseUnits[0].Payments.Captures[0].CreateTime
wstatus = "up"
due = date.AddDate(1, 0, 0)
name = capture.Payer.Name.GivenName
surname = capture.Payer.Name.Surname
email = capture.Payer.EmailAddress
phone = capture.Payer.Phone.PhoneNumber.NationalNumber
country = capture.Payer.Address.CountryCode
var pkey int
newSite := db.QueryRow(`SELECT id FROM sites WHERE folder = $1`, directory).Scan(&pkey)
if newSite == sql.ErrNoRows {
if err := db.QueryRow(
`INSERT INTO sites (folder, status, due, name, sur, email, phone, code)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
RETURNING id`,
directory, wstatus, due,
name, surname, email, phone, country).Scan(&pkey); err != nil {
log.Printf("Error: Could not register site to database: %v", err)
return
}
} else {
if err := db.QueryRow(
`UPDATE sites SET due = due + INTERVAL '1 year'
WHERE id = $1
RETURNING id`,
pkey).Scan(&pkey); err != nil {
log.Fatalf("Error: Could not update due date: %v", err)
return
}
}
if _, err := db.Exec(
`INSERT INTO payments (capture, site, amount, currency, date, status)
VALUES ($1, $2, $3, $4, $5, $6)`,
id, pkey, amount, currency, date, pstatus); err != nil {
log.Printf("Error: Could not register payment to database: %v", err)
return
}
return
}
func CreateOrder(w http.ResponseWriter, r *http.Request) {
token, err := Token()
if err != nil {
http.Error(w,
"Failed to get access token",
http.StatusInternalServerError)
return
}
type Amount struct {
CurrencyCode string `json:"currency_code"`
Value string `json:"value"`
}
type PurchaseUnits struct {
Amount Amount `json:"amount"`
}
type Address struct {
CountryCode string `json:"country_code"`
}
type Paypal struct {
Address Address `json:"address"`
}
type PaymentSource struct {
Paypal Paypal `json:"paypal"`
}
type ApplicationContext struct {
ShippingPreference string `json:"shipping_preference"`
}
type Order struct {
Intent string `json:"intent"`
PurchaseUnits []PurchaseUnits `json:"purchase_units"`
PaymentSource PaymentSource `json:"payment_source"`
ApplicationContext ApplicationContext `json:"application_context"`
}
// This payload will fill out defaults in PayPal
// checkout window (CR code, no shipping, etc)
order := Order{
Intent: "CAPTURE",
PurchaseUnits: []PurchaseUnits{{Amount: Amount{
CurrencyCode: "USD",
Value: os.Getenv("PRICE"),
}}},
PaymentSource: PaymentSource{Paypal: Paypal{Address: Address{
CountryCode: "CR",
}}},
ApplicationContext: ApplicationContext{
ShippingPreference: "NO_SHIPPING",
},
}
payload, err := json.Marshal(order)
req, err := http.NewRequest("POST",
os.Getenv("BASE_URL")+"/v2/checkout/orders",
bytes.NewBuffer(payload))
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Authorization", "Bearer "+token)
raw, err := http.DefaultClient.Do(req)
if err != nil {
http.Error(w,
"Failed to send request",
http.StatusInternalServerError)
return
}
defer raw.Body.Close()
var response struct {
ID string `json:"id"`
}
if err := json.NewDecoder(raw.Body).Decode(&response); err != nil {
http.Error(w,
"Failed to decode response",
http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(response)
return
}
func CaptureOrder(w http.ResponseWriter, r *http.Request) {
info, err := io.ReadAll(r.Body)
if err != nil {
http.Error(w,
"Failed to read request body",
http.StatusInternalServerError)
return
}
var cart struct {
Directory string `json:"directory"`
}
err = json.Unmarshal(info, &cart)
if err != nil {
http.Error(w,
"Failed to parse request body",
http.StatusBadRequest)
return
}
directory := cart.Directory
path := strings.TrimPrefix(r.URL.Path, "/api/orders/")
parts := strings.Split(path, "/")
orderID := parts[0]
if orderID == "" {
http.Error(w,
"Failed to get orderID from client URL",
http.StatusInternalServerError)
return
}
token, err := Token()
if err != nil {
http.Error(w,
"Failed to get access token",
http.StatusInternalServerError)
return
}
req, err := http.NewRequest("POST",
os.Getenv("BASE_URL")+"/v2/checkout/orders/"+orderID+"/capture",
nil)
req.Header.Set("Authorization", "Bearer "+token)
req.Header.Set("Content-Type", "application/json")
raw, err := http.DefaultClient.Do(req)
if err != nil {
http.Error(w,
"Failed to send request",
http.StatusInternalServerError)
return
}
defer raw.Body.Close()
body, err := io.ReadAll(raw.Body)
if err != nil {
http.Error(w,
"Failed to read response body",
http.StatusInternalServerError)
return
}
var capture Capture
if err := json.Unmarshal(body, &capture); err != nil {
http.Error(w,
"Failed to decode response into capture",
http.StatusInternalServerError)
return
}
var receipt = struct {
PurchaseUnits []struct {
Payments struct {
Captures []struct {
ID string `json:"id"`
Status string `json:"status"`
} `json:"captures"`
} `json:"payments"`
} `json:"purchase_units"`
}{}
if err := json.Unmarshal(body, &receipt); err != nil {
http.Error(w,
"Failed to decode response into receipt",
http.StatusInternalServerError)
return
}
RegisterOrder(capture, directory)
w.Header().Set("Content-Type", "application/json")
if err := json.NewEncoder(w).Encode(receipt); err != nil {
http.Error(w,
"Failed to encode response",
http.StatusInternalServerError)
return
}
return
}