mirror of
https://github.com/tavo-wasd-gh/conex-builder.git
synced 2025-06-07 04:03:29 -06:00
separate in files
This commit is contained in:
parent
072901d2f5
commit
7c50792f26
5 changed files with 398 additions and 371 deletions
|
@ -48,8 +48,8 @@ SELECT * FROM sites;
|
||||||
|
|
||||||
#+RESULTS:
|
#+RESULTS:
|
||||||
| id | folder | status | due | name | sur | email | phone | code | raw |
|
| id | folder | status | due | name | sur | email | phone | code | raw |
|
||||||
|----+-----------+--------+------------------------+------+-----+---------------------------------------+------------+------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
|----+-----------+--------+------------------------+------+-----+---------------------------------------+------------+------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||||
| 2 | gofitness | up | 2025-09-01 00:18:06-06 | John | Doe | sb-8kx8c32267916@personal.example.com | 5068031951 | CR | {"time": 1725171485533, "blocks": [{"id": "hLu8z-l1u5", "data": {"text": "asdfasfasdf", "level": 2}, "type": "header"}, {"id": "frc77fNxnu", "data": {"text": "asdfasfasdf"}, "type": "paragraph"}], "version": "2.30.5"} |
|
| 1 | gofitness | down | 2025-09-01 01:18:08-06 | John | Doe | sb-8kx8c32267916@personal.example.com | 5068031951 | CR | {"time": 1725175087581, "blocks": [{"id": "dGd_EGtUWN", "data": {"text": "asdfasdfasdfasdf", "level": 2}, "type": "header"}, {"id": "z9UoyyRH_4", "data": {"text": "asdfasdfasdfasdfa Hi<br>"}, "type": "paragraph"}], "version": "2.30.5"} |
|
||||||
|
|
||||||
** Payments table
|
** Payments table
|
||||||
|
|
||||||
|
|
78
server/db.go
Normal file
78
server/db.go
Normal file
|
@ -0,0 +1,78 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"database/sql"
|
||||||
|
"encoding/json"
|
||||||
|
"log"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
_ "github.com/lib/pq"
|
||||||
|
)
|
||||||
|
|
||||||
|
func RegisterSite(capture Capture, directory string, editorData json.RawMessage) {
|
||||||
|
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 = "down"
|
||||||
|
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, raw)
|
||||||
|
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)
|
||||||
|
RETURNING id`,
|
||||||
|
directory, wstatus, due,
|
||||||
|
name, surname, email, phone, country,
|
||||||
|
editorData).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
|
||||||
|
}
|
49
server/init.go
Normal file
49
server/init.go
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"database/sql"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/joho/godotenv"
|
||||||
|
_ "github.com/lib/pq"
|
||||||
|
)
|
||||||
|
|
||||||
|
var db *sql.DB
|
||||||
|
|
||||||
|
func initialize() {
|
||||||
|
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 shutdown() {
|
||||||
|
if db != nil {
|
||||||
|
if err := db.Close(); err != nil {
|
||||||
|
log.Fatalf("Error: Can't close database connection: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
371
server/main.go
371
server/main.go
|
@ -1,89 +1,16 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"database/sql"
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
"strings"
|
|
||||||
"syscall"
|
"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() {
|
func main() {
|
||||||
|
initialize()
|
||||||
|
|
||||||
http.HandleFunc("/api/orders", CreateOrder)
|
http.HandleFunc("/api/orders", CreateOrder)
|
||||||
http.HandleFunc("/api/orders/", CaptureOrder)
|
http.HandleFunc("/api/orders/", CaptureOrder)
|
||||||
http.Handle("/", http.FileServer(http.Dir("./public")))
|
http.Handle("/", http.FileServer(http.Dir("./public")))
|
||||||
|
@ -101,298 +28,6 @@ func main() {
|
||||||
|
|
||||||
<-stop
|
<-stop
|
||||||
|
|
||||||
defer func() {
|
shutdown()
|
||||||
if db != nil {
|
|
||||||
if err := db.Close(); err != nil {
|
|
||||||
log.Fatalf("Error: Can't close database connection: %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
log.Println("Server shutdown gracefully.")
|
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, editorData json.RawMessage) {
|
|
||||||
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 = "down"
|
|
||||||
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, raw)
|
|
||||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)
|
|
||||||
RETURNING id`,
|
|
||||||
directory, wstatus, due,
|
|
||||||
name, surname, email, phone, country,
|
|
||||||
editorData).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"`
|
|
||||||
EditorData json.RawMessage `json:"editor_data"`
|
|
||||||
}
|
|
||||||
|
|
||||||
err = json.Unmarshal(info, &cart)
|
|
||||||
if err != nil {
|
|
||||||
http.Error(w,
|
|
||||||
"Failed to parse request body",
|
|
||||||
http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
directory := cart.Directory
|
|
||||||
editorData := cart.EditorData
|
|
||||||
if err != nil {
|
|
||||||
http.Error(w,
|
|
||||||
"Failed to parse request body",
|
|
||||||
http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
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, editorData)
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
265
server/paypal.go
Normal file
265
server/paypal.go
Normal file
|
@ -0,0 +1,265 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
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 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 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"`
|
||||||
|
EditorData json.RawMessage `json:"editor_data"`
|
||||||
|
}
|
||||||
|
|
||||||
|
err = json.Unmarshal(info, &cart)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w,
|
||||||
|
"Failed to parse request body",
|
||||||
|
http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
directory := cart.Directory
|
||||||
|
editorData := cart.EditorData
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w,
|
||||||
|
"Failed to parse request body",
|
||||||
|
http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
RegisterSite(capture, directory, editorData)
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue