210 lines
6.2 KiB
Go
210 lines
6.2 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"log"
|
|
"net/http"
|
|
"time"
|
|
|
|
"git.k6n.net/go-cart-actor/pkg/actor"
|
|
"git.k6n.net/go-cart-actor/pkg/cart"
|
|
"git.k6n.net/go-cart-actor/pkg/messages"
|
|
"github.com/matst80/go-redis-inventory/pkg/inventory"
|
|
amqp "github.com/rabbitmq/amqp091-go"
|
|
)
|
|
|
|
var tpl = `<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8" />
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
<title>s10r testing - checkout</title>
|
|
</head>
|
|
|
|
<body>
|
|
%s
|
|
</body>
|
|
</html>
|
|
`
|
|
|
|
func (a *App) getGrainFromOrder(ctx context.Context, order *CheckoutOrder) (*cart.CartGrain, error) {
|
|
cartId, ok := cart.ParseCartId(order.MerchantReference1)
|
|
if !ok {
|
|
return nil, fmt.Errorf("invalid cart id in order reference: %s", order.MerchantReference1)
|
|
}
|
|
grain, err := a.pool.Get(ctx, uint64(cartId))
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to get cart grain: %w", err)
|
|
}
|
|
return grain, nil
|
|
}
|
|
|
|
func (a *App) HandleCheckoutRequests(amqpUrl string, mux *http.ServeMux, inventoryService inventory.InventoryService) {
|
|
conn, err := amqp.Dial(amqpUrl)
|
|
if err != nil {
|
|
log.Fatalf("failed to connect to RabbitMQ: %v", err)
|
|
}
|
|
|
|
amqpListener := actor.NewAmqpListener(conn, func(id uint64, msg []actor.ApplyResult) (any, error) {
|
|
return &CartChangeEvent{
|
|
CartId: cart.CartId(id),
|
|
Mutations: msg,
|
|
}, nil
|
|
})
|
|
amqpListener.DefineTopics()
|
|
a.pool.AddListener(amqpListener)
|
|
orderHandler := NewAmqpOrderHandler(conn)
|
|
orderHandler.DefineQueue()
|
|
|
|
mux.HandleFunc("/push", func(w http.ResponseWriter, r *http.Request) {
|
|
log.Printf("Klarna order confirmation push, method: %s", r.Method)
|
|
if r.Method != http.MethodPost {
|
|
w.WriteHeader(http.StatusMethodNotAllowed)
|
|
return
|
|
}
|
|
orderId := r.URL.Query().Get("order_id")
|
|
log.Printf("Order confirmation push: %s", orderId)
|
|
|
|
order, err := a.klarnaClient.GetOrder(r.Context(), orderId)
|
|
|
|
if err != nil {
|
|
log.Printf("Error creating request: %v\n", err)
|
|
w.WriteHeader(http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
grain, err := a.getGrainFromOrder(r.Context(), order)
|
|
if err != nil {
|
|
logger.ErrorContext(r.Context(), "Unable to get grain from klarna order", "error", err.Error())
|
|
w.WriteHeader(http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
if inventoryService != nil {
|
|
inventoryRequests := getInventoryRequests(grain.Items)
|
|
err = inventoryService.ReserveInventory(r.Context(), inventoryRequests...)
|
|
|
|
if err != nil {
|
|
logger.WarnContext(r.Context(), "placeorder inventory reservation failed")
|
|
w.WriteHeader(http.StatusNotAcceptable)
|
|
return
|
|
}
|
|
a.pool.Apply(r.Context(), uint64(grain.Id), &messages.InventoryReserved{
|
|
Id: grain.Id.String(),
|
|
Status: "success",
|
|
})
|
|
}
|
|
|
|
err = confirmOrder(r.Context(), order, orderHandler)
|
|
if err != nil {
|
|
log.Printf("Error confirming order: %v\n", err)
|
|
w.WriteHeader(http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
err = triggerOrderCompleted(r.Context(), a.server, order)
|
|
if err != nil {
|
|
log.Printf("Error processing cart message: %v\n", err)
|
|
w.WriteHeader(http.StatusInternalServerError)
|
|
return
|
|
}
|
|
err = a.klarnaClient.AcknowledgeOrder(r.Context(), orderId)
|
|
if err != nil {
|
|
log.Printf("Error acknowledging order: %v\n", err)
|
|
}
|
|
|
|
w.WriteHeader(http.StatusOK)
|
|
})
|
|
|
|
mux.HandleFunc("GET /checkout", a.server.CheckoutHandler(func(order *CheckoutOrder, w http.ResponseWriter) error {
|
|
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
|
w.Header().Set("Permissions-Policy", "payment=(self \"https://js.stripe.com\" \"https://m.stripe.network\" \"https://js.playground.kustom.co\")")
|
|
w.WriteHeader(http.StatusOK)
|
|
_, err := fmt.Fprintf(w, tpl, order.HTMLSnippet)
|
|
return err
|
|
}))
|
|
|
|
mux.HandleFunc("GET /confirmation/{order_id}", func(w http.ResponseWriter, r *http.Request) {
|
|
|
|
orderId := r.PathValue("order_id")
|
|
order, err := a.klarnaClient.GetOrder(r.Context(), orderId)
|
|
|
|
if err != nil {
|
|
w.WriteHeader(http.StatusInternalServerError)
|
|
w.Write([]byte(err.Error()))
|
|
return
|
|
}
|
|
|
|
// Apply ConfirmationViewed mutation
|
|
cartId, ok := cart.ParseCartId(order.MerchantReference1)
|
|
if ok {
|
|
a.pool.Apply(r.Context(), uint64(cartId), &messages.ConfirmationViewed{})
|
|
}
|
|
|
|
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
|
if order.Status == "checkout_complete" {
|
|
http.SetCookie(w, &http.Cookie{
|
|
Name: "cartid",
|
|
Value: "",
|
|
Path: "/",
|
|
Secure: true,
|
|
HttpOnly: true,
|
|
Expires: time.Unix(0, 0),
|
|
SameSite: http.SameSiteLaxMode,
|
|
})
|
|
}
|
|
|
|
w.WriteHeader(http.StatusOK)
|
|
fmt.Fprintf(w, tpl, order.HTMLSnippet)
|
|
})
|
|
mux.HandleFunc("/notification", func(w http.ResponseWriter, r *http.Request) {
|
|
log.Printf("Klarna order notification, method: %s", r.Method)
|
|
logger.InfoContext(r.Context(), "Klarna order notification received", "method", r.Method)
|
|
if r.Method != "POST" {
|
|
w.WriteHeader(http.StatusMethodNotAllowed)
|
|
return
|
|
}
|
|
order := &CheckoutOrder{}
|
|
err := json.NewDecoder(r.Body).Decode(order)
|
|
if err != nil {
|
|
w.WriteHeader(http.StatusBadRequest)
|
|
}
|
|
log.Printf("Klarna order notification: %s", order.ID)
|
|
logger.InfoContext(r.Context(), "Klarna order notification received", "order_id", order.ID)
|
|
|
|
w.WriteHeader(http.StatusOK)
|
|
})
|
|
mux.HandleFunc("POST /validate", func(w http.ResponseWriter, r *http.Request) {
|
|
log.Printf("Klarna order validation, method: %s", r.Method)
|
|
if r.Method != "POST" {
|
|
w.WriteHeader(http.StatusMethodNotAllowed)
|
|
return
|
|
}
|
|
order := &CheckoutOrder{}
|
|
err := json.NewDecoder(r.Body).Decode(order)
|
|
if err != nil {
|
|
w.WriteHeader(http.StatusBadRequest)
|
|
}
|
|
logger.InfoContext(r.Context(), "Klarna order validation received", "order_id", order.ID, "cart_id", order.MerchantReference1)
|
|
grain, err := a.getGrainFromOrder(r.Context(), order)
|
|
if err != nil {
|
|
logger.ErrorContext(r.Context(), "Unable to get grain from klarna order", "error", err.Error())
|
|
w.WriteHeader(http.StatusInternalServerError)
|
|
return
|
|
}
|
|
if inventoryService != nil {
|
|
inventoryRequests := getInventoryRequests(grain.Items)
|
|
_, err = inventoryService.ReservationCheck(r.Context(), inventoryRequests...)
|
|
if err != nil {
|
|
logger.WarnContext(r.Context(), "placeorder inventory check failed")
|
|
w.WriteHeader(http.StatusNotAcceptable)
|
|
return
|
|
}
|
|
}
|
|
|
|
w.WriteHeader(http.StatusOK)
|
|
})
|
|
}
|