Complete refactor to new grpc control plane and only http proxy for carts #4
@@ -113,7 +113,7 @@ metadata:
|
|||||||
arch: arm64
|
arch: arm64
|
||||||
name: cart-actor-arm64
|
name: cart-actor-arm64
|
||||||
spec:
|
spec:
|
||||||
replicas: 0
|
replicas: 3
|
||||||
selector:
|
selector:
|
||||||
matchLabels:
|
matchLabels:
|
||||||
app: cart-actor
|
app: cart-actor
|
||||||
|
|||||||
102
frames.go
102
frames.go
@@ -1,102 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
// Minimal frame abstractions retained after removal of the legacy TCP/frame
|
|
||||||
// networking layer. These types remain only to avoid a wide cascading refactor
|
|
||||||
// across existing grain / pool logic that still constructs and passes
|
|
||||||
// FrameWithPayload objects internally.
|
|
||||||
//
|
|
||||||
// The original responsibilities this replaces:
|
|
||||||
// - Binary framing, checksums, network IO
|
|
||||||
// - Distinction between request / reply frame types
|
|
||||||
//
|
|
||||||
// What remains:
|
|
||||||
// - A light weight container (FrameWithPayload) used as an in‑process
|
|
||||||
// envelope for status code + typed marker + payload bytes (JSON or proto).
|
|
||||||
// - Message / status constants referenced in existing code paths.
|
|
||||||
//
|
|
||||||
// Recommended future cleanup (post‑migration):
|
|
||||||
// - Remove FrameType entirely and replace with enumerated semantic results
|
|
||||||
// or error values.
|
|
||||||
// - Replace FrameWithPayload with a struct { Status int; Data []byte }.
|
|
||||||
// - Remove remote_* reply type branching once all callers rely on gRPC
|
|
||||||
// status + strongly typed responses.
|
|
||||||
//
|
|
||||||
// For now we keep this minimal surface to keep the gRPC migration focused.
|
|
||||||
|
|
||||||
type (
|
|
||||||
// FrameType is a symbolic identifier carried through existing code paths.
|
|
||||||
// No ordering or bit semantics are required anymore.
|
|
||||||
FrameType uint32
|
|
||||||
StatusCode uint32
|
|
||||||
)
|
|
||||||
|
|
||||||
type Frame struct {
|
|
||||||
Type FrameType
|
|
||||||
StatusCode StatusCode
|
|
||||||
Length uint32
|
|
||||||
// Checksum retained for compatibility; no longer validated.
|
|
||||||
Checksum uint32
|
|
||||||
}
|
|
||||||
|
|
||||||
// FrameWithPayload wraps a Frame with an opaque payload.
|
|
||||||
// Payload usually contains JSON encoded cart state or an error message.
|
|
||||||
type FrameWithPayload struct {
|
|
||||||
Frame
|
|
||||||
Payload []byte
|
|
||||||
}
|
|
||||||
|
|
||||||
// -----------------------------------------------------------------------------
|
|
||||||
// Legacy Frame Type Constants (minimal subset still referenced)
|
|
||||||
// -----------------------------------------------------------------------------
|
|
||||||
const (
|
|
||||||
RemoteGetState = FrameType(0x01)
|
|
||||||
RemoteHandleMutation = FrameType(0x02)
|
|
||||||
ResponseBody = FrameType(0x03) // (rarely used; kept for completeness)
|
|
||||||
RemoteGetStateReply = FrameType(0x04)
|
|
||||||
RemoteHandleMutationReply = FrameType(0x05)
|
|
||||||
RemoteCreateOrderReply = FrameType(0x06)
|
|
||||||
)
|
|
||||||
|
|
||||||
// MakeFrameWithPayload constructs an in‑process frame wrapper.
|
|
||||||
// Length & Checksum are filled for backward compatibility (no validation logic
|
|
||||||
// depends on the checksum anymore).
|
|
||||||
func MakeFrameWithPayload(msg FrameType, statusCode StatusCode, payload []byte) FrameWithPayload {
|
|
||||||
length := uint32(len(payload))
|
|
||||||
return FrameWithPayload{
|
|
||||||
Frame: Frame{
|
|
||||||
Type: msg,
|
|
||||||
StatusCode: statusCode,
|
|
||||||
Length: length,
|
|
||||||
Checksum: (uint32(msg) + uint32(statusCode) + length) / 8, // simple legacy formula
|
|
||||||
},
|
|
||||||
Payload: payload,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Clone creates a shallow copy of the frame, duplicating the payload slice.
|
|
||||||
func (f *FrameWithPayload) Clone() *FrameWithPayload {
|
|
||||||
if f == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
cp := make([]byte, len(f.Payload))
|
|
||||||
copy(cp, f.Payload)
|
|
||||||
return &FrameWithPayload{
|
|
||||||
Frame: f.Frame,
|
|
||||||
Payload: cp,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewErrorFrame helper for creating an error frame with a textual payload.
|
|
||||||
func NewErrorFrame(msg FrameType, code StatusCode, err error) FrameWithPayload {
|
|
||||||
var b []byte
|
|
||||||
if err != nil {
|
|
||||||
b = []byte(err.Error())
|
|
||||||
}
|
|
||||||
return MakeFrameWithPayload(msg, code, b)
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsSuccess returns true if the status code indicates success in the
|
|
||||||
// conventional HTTP style range (200–299). This mirrors previous usage patterns.
|
|
||||||
func (f *FrameWithPayload) IsSuccess() bool {
|
|
||||||
return f != nil && f.StatusCode >= 200 && f.StatusCode < 300
|
|
||||||
}
|
|
||||||
6
main.go
6
main.go
@@ -249,20 +249,20 @@ func main() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
cartId := ToCartId(cookie.Value)
|
cartId := ToCartId(cookie.Value)
|
||||||
_, err = syncedServer.pool.Apply(cartId, getCheckoutOrder(r.Host, cartId))
|
order, err = syncedServer.CreateOrUpdateCheckout(r.Host, cartId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
w.WriteHeader(http.StatusInternalServerError)
|
||||||
w.Write([]byte(err.Error()))
|
w.Write([]byte(err.Error()))
|
||||||
}
|
}
|
||||||
// v2: Apply now returns *CartGrain; order creation handled inside grain (no payload to unmarshal)
|
// v2: Apply now returns *CartGrain; order creation handled inside grain (no payload to unmarshal)
|
||||||
} else {
|
} else {
|
||||||
prevOrder, err := KlarnaInstance.GetOrder(orderId)
|
order, err = KlarnaInstance.GetOrder(orderId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
w.WriteHeader(http.StatusBadRequest)
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
w.Write([]byte(err.Error()))
|
w.Write([]byte(err.Error()))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
order = prevOrder
|
|
||||||
}
|
}
|
||||||
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
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.Header().Set("Permissions-Policy", "payment=(self \"https://js.stripe.com\" \"https://m.stripe.network\" \"https://js.playground.kustom.co\")")
|
||||||
|
|||||||
@@ -216,48 +216,51 @@ func (s *PoolServer) HandleConfirmation(w http.ResponseWriter, r *http.Request,
|
|||||||
return json.NewEncoder(w).Encode(order)
|
return json.NewEncoder(w).Encode(order)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *PoolServer) HandleCheckout(w http.ResponseWriter, r *http.Request, id CartId) error {
|
func (s *PoolServer) CreateOrUpdateCheckout(host string, id CartId) (*CheckoutOrder, error) {
|
||||||
// Build checkout meta (URLs derived from host)
|
|
||||||
meta := &CheckoutMeta{
|
meta := &CheckoutMeta{
|
||||||
Terms: fmt.Sprintf("https://%s/terms", r.Host),
|
Terms: fmt.Sprintf("https://%s/terms", host),
|
||||||
Checkout: fmt.Sprintf("https://%s/checkout?order_id={checkout.order.id}", r.Host),
|
Checkout: fmt.Sprintf("https://%s/checkout?order_id={checkout.order.id}", host),
|
||||||
Confirmation: fmt.Sprintf("https://%s/confirmation/{checkout.order.id}", r.Host),
|
Confirmation: fmt.Sprintf("https://%s/confirmation/{checkout.order.id}", host),
|
||||||
Validation: fmt.Sprintf("https://%s/validate", r.Host),
|
Validation: fmt.Sprintf("https://%s/validate", host),
|
||||||
Push: fmt.Sprintf("https://%s/push?order_id={checkout.order.id}", r.Host),
|
Push: fmt.Sprintf("https://%s/push?order_id={checkout.order.id}", host),
|
||||||
Country: getCountryFromHost(r.Host),
|
Country: getCountryFromHost(host),
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get current grain state (may be local or remote)
|
// Get current grain state (may be local or remote)
|
||||||
grain, err := s.pool.Get(id)
|
grain, err := s.pool.Get(id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build pure checkout payload
|
// Build pure checkout payload
|
||||||
payload, _, err := BuildCheckoutOrderPayload(grain, meta)
|
payload, _, err := BuildCheckoutOrderPayload(grain, meta)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Call Klarna (create or update)
|
|
||||||
var klarnaOrder *CheckoutOrder
|
|
||||||
if grain.OrderReference != "" {
|
if grain.OrderReference != "" {
|
||||||
klarnaOrder, err = KlarnaInstance.UpdateOrder(grain.OrderReference, bytes.NewReader(payload))
|
return KlarnaInstance.UpdateOrder(grain.OrderReference, bytes.NewReader(payload))
|
||||||
} else {
|
} else {
|
||||||
klarnaOrder, err = KlarnaInstance.CreateOrder(bytes.NewReader(payload))
|
return KlarnaInstance.CreateOrder(bytes.NewReader(payload))
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *PoolServer) ApplyCheckoutStarted(klarnaOrder *CheckoutOrder, id CartId) (*CartGrain, error) {
|
||||||
|
// Persist initialization state via mutation (best-effort)
|
||||||
|
return s.pool.Apply(id, &messages.InitializeCheckout{
|
||||||
|
OrderId: klarnaOrder.ID,
|
||||||
|
Status: klarnaOrder.Status,
|
||||||
|
PaymentInProgress: true,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *PoolServer) HandleCheckout(w http.ResponseWriter, r *http.Request, id CartId) error {
|
||||||
|
klarnaOrder, err := s.CreateOrUpdateCheckout(r.Host, id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Persist initialization state via mutation (best-effort)
|
s.ApplyCheckoutStarted(klarnaOrder, id)
|
||||||
if _, applyErr := s.pool.Apply(id, &messages.InitializeCheckout{
|
|
||||||
OrderId: klarnaOrder.ID,
|
|
||||||
Status: klarnaOrder.Status,
|
|
||||||
PaymentInProgress: true,
|
|
||||||
}); applyErr != nil {
|
|
||||||
log.Printf("InitializeCheckout apply error: %v", applyErr)
|
|
||||||
}
|
|
||||||
|
|
||||||
w.Header().Set("Content-Type", "application/json")
|
w.Header().Set("Content-Type", "application/json")
|
||||||
return json.NewEncoder(w).Encode(klarnaOrder)
|
return json.NewEncoder(w).Encode(klarnaOrder)
|
||||||
|
|||||||
Reference in New Issue
Block a user