diff --git a/src/frontend/handlers.go b/src/frontend/handlers.go index 014be97..a03be31 100644 --- a/src/frontend/handlers.go +++ b/src/frontend/handlers.go @@ -48,7 +48,7 @@ func ensureSessionID(next http.HandlerFunc) http.HandlerFunc { } func (fe *frontendServer) homeHandler(w http.ResponseWriter, r *http.Request) { - log.Printf("[home] session_id=%+v currency=%s", sessionID(r), currentCurrency(r)) + log.Printf("[home] session_id=%s currency=%s", sessionID(r), currentCurrency(r)) currencies, err := fe.getCurrencies(r.Context()) if err != nil { http.Error(w, fmt.Sprintf("could not retrieve currencies: %+v", err), http.StatusInternalServerError) @@ -150,7 +150,7 @@ func (fe *frontendServer) addToCartHandler(w http.ResponseWriter, r *http.Reques http.Error(w, "invalid form input", http.StatusBadRequest) return } - log.Printf("[addToCart] product_id=%s qty=%d session_id=%+v", productID, quantity, sessionID(r)) + log.Printf("[addToCart] product_id=%s qty=%d session_id=%s", productID, quantity, sessionID(r)) p, err := fe.getProduct(r.Context(), productID) if err != nil { @@ -167,7 +167,7 @@ func (fe *frontendServer) addToCartHandler(w http.ResponseWriter, r *http.Reques } func (fe *frontendServer) emptyCartHandler(w http.ResponseWriter, r *http.Request) { - log.Printf("[emptyCart] session_id=%+v", sessionID(r)) + log.Printf("[emptyCart] session_id=%s", sessionID(r)) if err := fe.emptyCart(r.Context(), sessionID(r)); err != nil { http.Error(w, fmt.Sprintf("failed to empty cart: %+v", err), http.StatusInternalServerError) @@ -178,7 +178,7 @@ func (fe *frontendServer) emptyCartHandler(w http.ResponseWriter, r *http.Reques } func (fe *frontendServer) viewCartHandler(w http.ResponseWriter, r *http.Request) { - log.Printf("[viewCart] session_id=%+v", sessionID(r)) + log.Printf("[viewCart] session_id=%s", sessionID(r)) currencies, err := fe.getCurrencies(r.Context()) if err != nil { http.Error(w, fmt.Sprintf("could not retrieve currencies: %+v", err), http.StatusInternalServerError) @@ -196,12 +196,19 @@ func (fe *frontendServer) viewCartHandler(w http.ResponseWriter, r *http.Request return } + shippingCost, err := fe.getShippingQuote(r.Context(), cart, currentCurrency(r)) + if err != nil { + http.Error(w, fmt.Sprintf("failed to get shipping quote: %+v", err), http.StatusInternalServerError) + return + } + type cartItemView struct { Item *pb.Product Quantity int32 Price *pb.Money } items := make([]cartItemView, len(cart)) + totalPrice := pb.Money{CurrencyCode: currentCurrency(r)} for i, item := range cart { p, err := fe.getProduct(r.Context(), item.GetProductId()) if err != nil { @@ -219,15 +226,73 @@ func (fe *frontendServer) viewCartHandler(w http.ResponseWriter, r *http.Request Item: p, Quantity: item.GetQuantity(), Price: &multPrice} + totalPrice = money.Must(money.Sum(totalPrice, multPrice)) } + totalPrice = money.Must(money.Sum(totalPrice, *shippingCost)) + year := time.Now().Year() if err := templates.ExecuteTemplate(w, "cart", map[string]interface{}{ - "user_currency": currentCurrency(r), - "currencies": currencies, + "user_currency": currentCurrency(r), + "currencies": currencies, + "session_id": sessionID(r), + "recommendations": recommendations, + "cart_size": len(cart), + "shipping_cost": shippingCost, + "total_cost": totalPrice, + "items": items, + "expiration_years": []int{year, year + 1, year + 2, year + 3, year + 4}, + }); err != nil { + log.Println(err) + } +} + +func (fe *frontendServer) placeOrderHandler(w http.ResponseWriter, r *http.Request) { + log.Printf("[checkout] session_id=%s", sessionID(r)) + + var ( + email = r.FormValue("email") + streetAddress = r.FormValue("street_address") + zipCode, _ = strconv.ParseInt(r.FormValue("zip_code"), 10, 32) + city = r.FormValue("city") + state = r.FormValue("state") + country = r.FormValue("country") + ccNumber = r.FormValue("credit_card_number") + ccMonth, _ = strconv.ParseInt(r.FormValue("credit_card_expiration_month"), 10, 32) + ccYear, _ = strconv.ParseInt(r.FormValue("credit_card_expiration_year"), 10, 32) + ccCVV, _ = strconv.ParseInt(r.FormValue("credit_card_cvv"), 10, 32) + ) + + order, err := pb.NewCheckoutServiceClient(fe.checkoutSvcConn). + PlaceOrder(r.Context(), &pb.PlaceOrderRequest{ + Email: email, + CreditCard: &pb.CreditCardInfo{ + CreditCardNumber: ccNumber, + CreditCardExpirationMonth: int32(ccMonth), + CreditCardExpirationYear: int32(ccYear), + CreditCardCvv: int32(ccCVV)}, + UserId: sessionID(r), + UserCurrency: currentCurrency(r), + Address: &pb.Address{ + StreetAddress: streetAddress, + City: city, + State: state, + ZipCode: int32(zipCode), + Country: country}, + }) + if err != nil { + http.Error(w, fmt.Sprintf("failed to complete the order: %+v", err), http.StatusInternalServerError) + return + } + log.Printf("order #%s completed", order.GetOrder().GetOrderId()) + + order.GetOrder().GetItems() + recommendations, _ := fe.getRecommendations(r.Context(), sessionID(r), nil) + + if err := templates.ExecuteTemplate(w, "order", map[string]interface{}{ "session_id": sessionID(r), + "user_currency": currentCurrency(r), + "order": order.GetOrder(), "recommendations": recommendations, - "cart_size": len(cart), - "items": items, }); err != nil { log.Println(err) } @@ -256,7 +321,7 @@ func (fe *frontendServer) prepareCheckoutHandler(w http.ResponseWriter, r *http. } func (fe *frontendServer) logoutHandler(w http.ResponseWriter, r *http.Request) { - log.Printf("[home] session_id=%+v", sessionID(r)) + log.Printf("[logout] session_id=%s", sessionID(r)) for _, c := range r.Cookies() { c.Expires = time.Now().Add(-time.Hour * 24 * 365) c.MaxAge = -1 @@ -268,7 +333,7 @@ func (fe *frontendServer) logoutHandler(w http.ResponseWriter, r *http.Request) func (fe *frontendServer) setCurrencyHandler(w http.ResponseWriter, r *http.Request) { cur := r.FormValue("currency_code") - log.Printf("[setCurrency] session_id=%+v code=%s", sessionID(r), cur) + log.Printf("[setCurrency] session_id=%s code=%s", sessionID(r), cur) if cur != "" { http.SetCookie(w, &http.Cookie{ Name: cookieCurrency, diff --git a/src/frontend/main.go b/src/frontend/main.go index 0bd613d..763dc3b 100644 --- a/src/frontend/main.go +++ b/src/frontend/main.go @@ -43,6 +43,9 @@ type frontendServer struct { checkoutSvcAddr string checkoutSvcConn *grpc.ClientConn + + shippingSvcAddr string + shippingSvcConn *grpc.ClientConn } func main() { @@ -59,6 +62,7 @@ func main() { mustMapEnv(&svc.cartSvcAddr, "CART_SERVICE_ADDR") mustMapEnv(&svc.recommendationSvcAddr, "RECOMMENDATION_SERVICE_ADDR") mustMapEnv(&svc.checkoutSvcAddr, "CHECKOUT_SERVICE_ADDR") + mustMapEnv(&svc.shippingSvcAddr, "SHIPPING_SERVICE_ADDR") var err error svc.currencySvcConn, err = grpc.DialContext(ctx, svc.currencySvcAddr, grpc.WithInsecure()) @@ -77,6 +81,14 @@ func main() { if err != nil { log.Fatalf("failed to connect recommendation service at %s: %+v", svc.recommendationSvcAddr, err) } + svc.shippingSvcConn, err = grpc.DialContext(ctx, svc.shippingSvcAddr, grpc.WithInsecure()) + if err != nil { + log.Fatalf("failed to connect shipping service at %s: %+v", svc.shippingSvcAddr, err) + } + svc.checkoutSvcConn, err = grpc.DialContext(ctx, svc.checkoutSvcAddr, grpc.WithInsecure()) + if err != nil { + log.Fatalf("failed to connect checkout service at %s: %+v", svc.checkoutSvcAddr, err) + } r := mux.NewRouter() r.HandleFunc("/", ensureSessionID(svc.homeHandler)).Methods(http.MethodGet, http.MethodHead) @@ -86,7 +98,7 @@ func main() { r.HandleFunc("/cart/empty", ensureSessionID(svc.emptyCartHandler)).Methods(http.MethodPost) r.HandleFunc("/setCurrency", ensureSessionID(svc.setCurrencyHandler)).Methods(http.MethodPost) r.HandleFunc("/logout", svc.logoutHandler).Methods(http.MethodGet) - r.HandleFunc("/checkout", ensureSessionID(svc.prepareCheckoutHandler)).Methods(http.MethodGet, http.MethodHead) + r.HandleFunc("/cart/checkout", ensureSessionID(svc.placeOrderHandler)).Methods(http.MethodPost) r.PathPrefix("/static/").Handler(http.StripPrefix("/static/", http.FileServer(http.Dir("./static/")))) log.Printf("starting server on :" + srvPort) log.Fatal(http.ListenAndServe("localhost:"+srvPort, r)) diff --git a/src/frontend/rpc.go b/src/frontend/rpc.go index 4e589f3..07f24a8 100644 --- a/src/frontend/rpc.go +++ b/src/frontend/rpc.go @@ -76,6 +76,21 @@ func (fe *frontendServer) convertCurrency(ctx context.Context, money *pb.Money, ToCode: currency}) } +func (fe *frontendServer) getShippingQuote(ctx context.Context, items []*pb.CartItem, currency string) (*pb.Money, error) { + quote, err := pb.NewShippingServiceClient(fe.currencySvcConn).GetQuote(ctx, + &pb.GetQuoteRequest{ + Address: nil, + Items: items}) + if err != nil { + return nil, err + } + localized, err := fe.convertCurrency(ctx, quote.GetCostUsd(), currency) + if err != nil { + return nil, fmt.Errorf("failed to convert currency for shipping cost: %+v", err) + } + return localized, nil +} + func (fe *frontendServer) getRecommendations(ctx context.Context, userID string, productIDs []string) ([]*pb.Product, error) { resp, err := pb.NewRecommendationServiceClient(fe.recommendationSvcConn).ListRecommendations(ctx, &pb.ListRecommendationsRequest{UserId: userID, ProductIds: productIDs}) diff --git a/src/frontend/templates/cart.html b/src/frontend/templates/cart.html index 652e64d..790e710 100644 --- a/src/frontend/templates/cart.html +++ b/src/frontend/templates/cart.html @@ -10,11 +10,22 @@ Browse Products → {{ else }} -
Shipping Cost: {{ renderMoney .shipping_cost }}
+ Total Cost: {{ renderMoney .total_cost }} +