Anatomical “fugitive sheets” are illustrations of the body designed to display internal organs and structures using paper flaps. Their name arose from the frequency with which the accompanying sheets were torn or misplaced. This site reimagines the fugitive sheet as a misplaced code-snippet, framed within a randomly generated cut-out.
package sales import ( "context" "fmt" "time" "github.com/google/uuid" "github.com/jayfreestone/the-moon-up-above-04/internal/conversion" "github.com/jayfreestone/the-moon-up-above-04/internal/validation" "github.com/pkg/errors" ) type Service struct { repo Repository productRepo ProductRepository paymentProvider PaymentProvider // At some point this will be a map so we can pick per request } func NewService(repo Repository, productRepo ProductRepository, paymentProvider PaymentProvider) *Service { return &Service{ repo: repo, productRepo: productRepo, paymentProvider: paymentProvider, } } func (s *Service) GetBasket(wishlist Wishlist) (*Basket, error) { if err := wishlist.Validate(); err != nil { return nil, err } ids := wishlist.IDs() products, err := s.productRepo.GetByIDs(ids) if err != nil { return nil, errors.Wrap(err, "unable to fetch products for wishlist") } basket, err := wishlist.FulfillWith(products) if err != nil { return nil, errors.Wrap(err, "unable to create basket from wishlist") } return &basket, nil } // StartCheckout creates a new pending order and payment transaction. func (s *Service) StartCheckout(newOrder OrderRequest) (*Checkout, error) { if err := newOrder.Validate(); err != nil { return nil, err } // First we recreate the basket from the wishlist, confirming it's valid basket, err := s.GetBasket(*newOrder.Wishlist) if err != nil { return nil, errors.Wrap(err, "unable to create basket from wishlist") } // Double check that the client-provided total matches up with what we were expecting. // We only compare the totals, since the client won't be overly concerned if price allocation // is different. The user will have another chance to see the price before payment, but // this early check gives us a little peace of mind that the user has already seen the total. if basket.Total() != *newOrder.ExpectedTotal { return nil, &validation.Error{ Message: "Invalid order request", Errors: []validation.InputError{ { Field: conversion.String("Total"), Message: fmt.Sprintf("expected total (%v) did not match actual (%v)", basket.Total().Amount, newOrder.ExpectedTotal.Amount), }, }, } } order := NewOrderFromBasket(*basket) transaction, err := s.paymentProvider.CreateTransaction(context.TODO(), order) if err != nil { return nil, errors.Wrap(err, "unable to create transaction for order") } if err := s.repo.Create(*order); err != nil { return nil, errors.Wrap(err, "unable to create new order") } return &Checkout{ Order: order, Transaction: transaction, }, err } // CompleteCheckout marks the payment process as completed, transitioning the order's status and updating inventory. // Note that stock updates and order updates are *not* committed as one atomic operation, meaning it is possible // to end up in an inconsistent state in the unlikely event that one DB update succeeds and another does not. // Handling this would introduce a lot of complexity for (presumably) a rare scenario, hence it is left as an // understood risk rather than an oversight. func (s *Service) CompleteCheckout(orderID uuid.UUID) error { now := time.Now() order, err := s.GetOrderByID(orderID) if err != nil { return errors.Wrapf(err, "unable to get order %s in order to complete checkout", orderID) } order.MarkAsPaid() order.MarkModified(now) orderProducts, err := s.productRepo.GetByIDs(order.LineItems.ProductIDs()) if err != nil { return errors.Wrap(err, "unable to find products during checkout completion") } if err := order.LineItems.DeductInventory(orderProducts); err != nil { return errors.Wrap(err, "unable to deduct product inventory during checkout completion") } orderProducts.MarkModified(now) if err := s.productRepo.UpdateProducts(orderProducts); err != nil { return errors.Wrapf(err, "unable to update products for order with id %s", orderID) } if err := s.repo.Update(*order); err != nil { return errors.Wrapf(err, "unable to complete checkout for order with id %s", orderID) } return nil } func (s *Service) GetOrderByID(orderID uuid.UUID) (*Order, error) { order, err := s.repo.GetByID(orderID) if err != nil { return nil, errors.Wrap(err, fmt.Sprintf("unable to find order with id %s", orderID)) } return order, nil }