checkout backoffice
This commit is contained in:
@@ -18,19 +18,24 @@ import (
|
|||||||
|
|
||||||
"git.k6n.net/go-cart-actor/pkg/actor"
|
"git.k6n.net/go-cart-actor/pkg/actor"
|
||||||
"git.k6n.net/go-cart-actor/pkg/cart"
|
"git.k6n.net/go-cart-actor/pkg/cart"
|
||||||
|
"git.k6n.net/go-cart-actor/pkg/checkout"
|
||||||
"google.golang.org/protobuf/proto"
|
"google.golang.org/protobuf/proto"
|
||||||
)
|
)
|
||||||
|
|
||||||
type FileServer struct {
|
type FileServer struct {
|
||||||
// Define fields here
|
// Define fields here
|
||||||
dataDir string
|
dataDir string
|
||||||
|
checkoutDataDir string
|
||||||
storage actor.LogStorage[cart.CartGrain]
|
storage actor.LogStorage[cart.CartGrain]
|
||||||
|
checkoutStorage actor.LogStorage[checkout.CheckoutGrain]
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewFileServer(dataDir string, storage actor.LogStorage[cart.CartGrain]) *FileServer {
|
func NewFileServer(dataDir string, checkoutDataDir string, storage actor.LogStorage[cart.CartGrain], checkoutStorage actor.LogStorage[checkout.CheckoutGrain]) *FileServer {
|
||||||
return &FileServer{
|
return &FileServer{
|
||||||
dataDir: dataDir,
|
dataDir: dataDir,
|
||||||
|
checkoutDataDir: checkoutDataDir,
|
||||||
storage: storage,
|
storage: storage,
|
||||||
|
checkoutStorage: checkoutStorage,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -79,6 +84,12 @@ func appendFileInfo(info fs.FileInfo, out *CartFileInfo) *CartFileInfo {
|
|||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func appendCheckoutFileInfo(info fs.FileInfo, out *CheckoutFileInfo) *CheckoutFileInfo {
|
||||||
|
out.Size = info.Size()
|
||||||
|
out.Modified = info.ModTime()
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
// var cartFileRe = regexp.MustCompile(`^(\d+)\.events\.log$`)
|
// var cartFileRe = regexp.MustCompile(`^(\d+)\.events\.log$`)
|
||||||
|
|
||||||
func listCartFiles(dir string) ([]*CartFileInfo, error) {
|
func listCartFiles(dir string) ([]*CartFileInfo, error) {
|
||||||
@@ -112,6 +123,36 @@ func listCartFiles(dir string) ([]*CartFileInfo, error) {
|
|||||||
return out, nil
|
return out, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func listCheckoutFiles(dir string) ([]*CheckoutFileInfo, error) {
|
||||||
|
entries, err := os.ReadDir(dir)
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, os.ErrNotExist) {
|
||||||
|
return []*CheckoutFileInfo{}, nil
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
out := make([]*CheckoutFileInfo, 0)
|
||||||
|
for _, e := range entries {
|
||||||
|
if e.IsDir() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
id, valid := isValidFileId(e.Name())
|
||||||
|
if !valid {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
info, err := e.Info()
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
out = append(out, appendCheckoutFileInfo(info, &CheckoutFileInfo{
|
||||||
|
ID: fmt.Sprintf("%d", id),
|
||||||
|
CheckoutId: checkout.CheckoutId(id),
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
func readRawLogLines(path string) ([]json.RawMessage, error) {
|
func readRawLogLines(path string) ([]json.RawMessage, error) {
|
||||||
fh, err := os.Open(path)
|
fh, err := os.Open(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -158,6 +199,21 @@ func (fs *FileServer) CartsHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (fs *FileServer) CheckoutsHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
list, err := listCheckoutFiles(fs.checkoutDataDir)
|
||||||
|
if err != nil {
|
||||||
|
writeJSON(w, http.StatusInternalServerError, map[string]string{"error": err.Error()})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// sort by modified desc
|
||||||
|
sort.Slice(list, func(i, j int) bool { return list[i].Modified.After(list[j].Modified) })
|
||||||
|
|
||||||
|
writeJSON(w, http.StatusOK, map[string]any{
|
||||||
|
"count": len(list),
|
||||||
|
"checkouts": list,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func (fs *FileServer) PromotionsHandler(w http.ResponseWriter, r *http.Request) {
|
func (fs *FileServer) PromotionsHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
fileName := filepath.Join(fs.dataDir, "promotions.json")
|
fileName := filepath.Join(fs.dataDir, "promotions.json")
|
||||||
if r.Method == http.MethodGet {
|
if r.Method == http.MethodGet {
|
||||||
@@ -315,3 +371,69 @@ func (fs *FileServer) CartHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (fs *FileServer) CheckoutHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
idStr := r.PathValue("id")
|
||||||
|
if idStr == "" {
|
||||||
|
writeJSON(w, http.StatusBadRequest, JsonError{Error: "missing id"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
id, ok := isValidId(idStr)
|
||||||
|
if !ok {
|
||||||
|
writeJSON(w, http.StatusBadRequest, JsonError{Error: "invalid id"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// parse query parameters for filtering
|
||||||
|
query := r.URL.Query()
|
||||||
|
filterFunction := acceptAll
|
||||||
|
if maxIndexStr := query.Get("maxIndex"); maxIndexStr != "" {
|
||||||
|
log.Printf("filter maxIndex: %s", maxIndexStr)
|
||||||
|
maxIndex, err := strconv.Atoi(maxIndexStr)
|
||||||
|
if err != nil {
|
||||||
|
writeJSON(w, http.StatusBadRequest, JsonError{Error: "invalid maxIndex"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
filterFunction = acceptUntilIndex(maxIndex)
|
||||||
|
} else if untilStr := query.Get("until"); untilStr != "" {
|
||||||
|
log.Printf("filter until: %s", untilStr)
|
||||||
|
until, err := time.Parse(time.RFC3339, untilStr)
|
||||||
|
if err != nil {
|
||||||
|
writeJSON(w, http.StatusBadRequest, JsonError{Error: "invalid until timestamp"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
filterFunction = acceptUntilTimestamp(until)
|
||||||
|
}
|
||||||
|
// reconstruct state from event log if present
|
||||||
|
grain := checkout.NewCheckoutGrain(id, cart.CartId(id), 0, time.Now(), nil)
|
||||||
|
err := fs.checkoutStorage.LoadEventsFunc(r.Context(), id, grain, filterFunction)
|
||||||
|
if err != nil {
|
||||||
|
writeJSON(w, http.StatusInternalServerError, JsonError{Error: err.Error()})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
path := filepath.Join(fs.checkoutDataDir, fmt.Sprintf("%d.events.log", id))
|
||||||
|
info, err := os.Stat(path)
|
||||||
|
if err != nil && errors.Is(err, os.ErrNotExist) {
|
||||||
|
writeJSON(w, http.StatusNotFound, JsonError{Error: "checkout not found"})
|
||||||
|
return
|
||||||
|
} else if err != nil {
|
||||||
|
writeJSON(w, http.StatusInternalServerError, JsonError{Error: err.Error()})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
lines, err := readRawLogLines(path)
|
||||||
|
if err != nil {
|
||||||
|
writeJSON(w, http.StatusInternalServerError, JsonError{Error: err.Error()})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
writeJSON(w, http.StatusOK, map[string]any{
|
||||||
|
"id": id,
|
||||||
|
"checkoutId": checkout.CheckoutId(id).String(),
|
||||||
|
"state": grain,
|
||||||
|
"mutations": lines,
|
||||||
|
"meta": map[string]any{
|
||||||
|
"size": info.Size(),
|
||||||
|
"modified": info.ModTime(),
|
||||||
|
"path": path,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import (
|
|||||||
|
|
||||||
actor "git.k6n.net/go-cart-actor/pkg/actor"
|
actor "git.k6n.net/go-cart-actor/pkg/actor"
|
||||||
"git.k6n.net/go-cart-actor/pkg/cart"
|
"git.k6n.net/go-cart-actor/pkg/cart"
|
||||||
|
"git.k6n.net/go-cart-actor/pkg/checkout"
|
||||||
"github.com/matst80/go-redis-inventory/pkg/inventory"
|
"github.com/matst80/go-redis-inventory/pkg/inventory"
|
||||||
"github.com/matst80/slask-finder/pkg/messaging"
|
"github.com/matst80/slask-finder/pkg/messaging"
|
||||||
amqp "github.com/rabbitmq/amqp091-go"
|
amqp "github.com/rabbitmq/amqp091-go"
|
||||||
@@ -24,6 +25,13 @@ type CartFileInfo struct {
|
|||||||
Modified time.Time `json:"modified"`
|
Modified time.Time `json:"modified"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type CheckoutFileInfo struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
CheckoutId checkout.CheckoutId `json:"checkoutId"`
|
||||||
|
Size int64 `json:"size"`
|
||||||
|
Modified time.Time `json:"modified"`
|
||||||
|
}
|
||||||
|
|
||||||
func envOrDefault(key, def string) string {
|
func envOrDefault(key, def string) string {
|
||||||
if v := os.Getenv(key); v != "" {
|
if v := os.Getenv(key); v != "" {
|
||||||
return v
|
return v
|
||||||
@@ -97,7 +105,13 @@ func main() {
|
|||||||
reg := cart.NewCartMultationRegistry(cart.NewCartMutationContext(nil))
|
reg := cart.NewCartMultationRegistry(cart.NewCartMutationContext(nil))
|
||||||
diskStorage := actor.NewDiskStorage[cart.CartGrain](dataDir, reg)
|
diskStorage := actor.NewDiskStorage[cart.CartGrain](dataDir, reg)
|
||||||
|
|
||||||
fs := NewFileServer(dataDir, diskStorage)
|
checkoutDataDir := envOrDefault("CHECKOUT_DATA_DIR", "checkout-data")
|
||||||
|
_ = os.MkdirAll(checkoutDataDir, 0755)
|
||||||
|
|
||||||
|
regCheckout := checkout.NewCheckoutMutationRegistry(checkout.NewCheckoutMutationContext())
|
||||||
|
diskStorageCheckout := actor.NewDiskStorage[checkout.CheckoutGrain](checkoutDataDir, regCheckout)
|
||||||
|
|
||||||
|
fs := NewFileServer(dataDir, checkoutDataDir, diskStorage, diskStorageCheckout)
|
||||||
|
|
||||||
hub := NewHub()
|
hub := NewHub()
|
||||||
go hub.Run()
|
go hub.Run()
|
||||||
@@ -105,6 +119,8 @@ func main() {
|
|||||||
mux := http.NewServeMux()
|
mux := http.NewServeMux()
|
||||||
mux.HandleFunc("GET /carts", fs.CartsHandler)
|
mux.HandleFunc("GET /carts", fs.CartsHandler)
|
||||||
mux.HandleFunc("GET /cart/{id}", fs.CartHandler)
|
mux.HandleFunc("GET /cart/{id}", fs.CartHandler)
|
||||||
|
mux.HandleFunc("GET /checkouts", fs.CheckoutsHandler)
|
||||||
|
mux.HandleFunc("GET /checkout/{id}", fs.CheckoutHandler)
|
||||||
mux.HandleFunc("PUT /inventory/{locationId}/{sku}", func(w http.ResponseWriter, r *http.Request) {
|
mux.HandleFunc("PUT /inventory/{locationId}/{sku}", func(w http.ResponseWriter, r *http.Request) {
|
||||||
inventoryLocationId := inventory.LocationID(r.PathValue("locationId"))
|
inventoryLocationId := inventory.LocationID(r.PathValue("locationId"))
|
||||||
inventorySku := inventory.SKU(r.PathValue("sku"))
|
inventorySku := inventory.SKU(r.PathValue("sku"))
|
||||||
|
|||||||
@@ -147,6 +147,7 @@ func (s *CheckoutPoolServer) AdyenHookHandler(w http.ResponseWriter, r *http.Req
|
|||||||
PaymentId: item.PspReference,
|
PaymentId: item.PspReference,
|
||||||
Status: item.Success,
|
Status: item.Success,
|
||||||
Amount: item.Amount.Value,
|
Amount: item.Amount.Value,
|
||||||
|
CompletedAt: timestamppb.Now(),
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
msgs = append(msgs, &messages.PaymentDeclined{
|
msgs = append(msgs, &messages.PaymentDeclined{
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import (
|
|||||||
"git.k6n.net/go-cart-actor/pkg/checkout"
|
"git.k6n.net/go-cart-actor/pkg/checkout"
|
||||||
messages "git.k6n.net/go-cart-actor/proto/checkout"
|
messages "git.k6n.net/go-cart-actor/proto/checkout"
|
||||||
"github.com/matst80/go-redis-inventory/pkg/inventory"
|
"github.com/matst80/go-redis-inventory/pkg/inventory"
|
||||||
|
"google.golang.org/protobuf/types/known/timestamppb"
|
||||||
)
|
)
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -201,6 +202,7 @@ func (s *CheckoutPoolServer) KlarnaPushHandler(w http.ResponseWriter, r *http.Re
|
|||||||
ProcessorReference: &order.ID,
|
ProcessorReference: &order.ID,
|
||||||
Amount: int64(order.OrderAmount),
|
Amount: int64(order.OrderAmount),
|
||||||
Currency: order.PurchaseCurrency,
|
Currency: order.PurchaseCurrency,
|
||||||
|
CompletedAt: timestamppb.Now(),
|
||||||
})
|
})
|
||||||
|
|
||||||
// err = confirmOrder(r.Context(), order, orderHandler)
|
// err = confirmOrder(r.Context(), order, orderHandler)
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ spec:
|
|||||||
volumes:
|
volumes:
|
||||||
- name: data
|
- name: data
|
||||||
nfs:
|
nfs:
|
||||||
path: /i-data/7a8af061/nfs/cart-actor
|
path: /i-data/7a8af061/nfs/
|
||||||
server: 10.10.1.10
|
server: 10.10.1.10
|
||||||
serviceAccountName: default
|
serviceAccountName: default
|
||||||
containers:
|
containers:
|
||||||
@@ -76,6 +76,10 @@ spec:
|
|||||||
memory: "70Mi"
|
memory: "70Mi"
|
||||||
cpu: "1200m"
|
cpu: "1200m"
|
||||||
env:
|
env:
|
||||||
|
- name: DATA_DIR
|
||||||
|
value: "/data/cart-actor"
|
||||||
|
- name: CHECKOUT_DATA_DIR
|
||||||
|
value: "/data/checkout-actor"
|
||||||
- name: TZ
|
- name: TZ
|
||||||
value: "Europe/Stockholm"
|
value: "Europe/Stockholm"
|
||||||
- name: REDIS_ADDRESS
|
- name: REDIS_ADDRESS
|
||||||
@@ -180,6 +184,10 @@ spec:
|
|||||||
memory: "70Mi"
|
memory: "70Mi"
|
||||||
cpu: "1200m"
|
cpu: "1200m"
|
||||||
env:
|
env:
|
||||||
|
- name: DATA_DIR
|
||||||
|
value: "/data/cart-actor"
|
||||||
|
- name: CHECKOUT_DATA_DIR
|
||||||
|
value: "/data/checkout-actor"
|
||||||
- name: TZ
|
- name: TZ
|
||||||
value: "Europe/Stockholm"
|
value: "Europe/Stockholm"
|
||||||
- name: KLARNA_API_USERNAME
|
- name: KLARNA_API_USERNAME
|
||||||
|
|||||||
@@ -21,9 +21,13 @@ func HandlePaymentCompleted(g *CheckoutGrain, m *messages.PaymentCompleted) erro
|
|||||||
|
|
||||||
payment.ProcessorReference = m.ProcessorReference
|
payment.ProcessorReference = m.ProcessorReference
|
||||||
payment.Status = PaymentStatusSuccess
|
payment.Status = PaymentStatusSuccess
|
||||||
|
if m.Amount > 0 {
|
||||||
payment.Amount = m.Amount
|
payment.Amount = m.Amount
|
||||||
|
}
|
||||||
|
if m.Currency != "" {
|
||||||
payment.Currency = m.Currency
|
payment.Currency = m.Currency
|
||||||
payment.CompletedAt = &time.Time{}
|
}
|
||||||
|
|
||||||
if m.CompletedAt != nil {
|
if m.CompletedAt != nil {
|
||||||
*payment.CompletedAt = m.CompletedAt.AsTime()
|
*payment.CompletedAt = m.CompletedAt.AsTime()
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
Reference in New Issue
Block a user