cart and checkout
This commit is contained in:
+30
-11
@@ -41,6 +41,7 @@ type server struct {
|
||||
freg *flow.Registry
|
||||
flows *flowStore
|
||||
provider order.PaymentProvider
|
||||
taxProvider order.TaxProvider
|
||||
defaultFlow string
|
||||
dataDir string
|
||||
idem *idempotency.Store
|
||||
@@ -134,9 +135,12 @@ func main() {
|
||||
log.Fatalf("open idempotency store: %v", err)
|
||||
}
|
||||
|
||||
taxProvider := selectTaxProvider()
|
||||
|
||||
s := &server{
|
||||
pool: pool, applier: applier, storage: storage, reg: reg,
|
||||
engine: engine, freg: freg, flows: flows, provider: provider,
|
||||
taxProvider: taxProvider,
|
||||
defaultFlow: def.Name, dataDir: dataDir, idem: idem, logger: logger,
|
||||
}
|
||||
|
||||
@@ -150,12 +154,14 @@ func main() {
|
||||
mux.HandleFunc("GET /api/orders/{id}", s.handleGet)
|
||||
mux.HandleFunc("GET /api/orders/{id}/events", s.handleEvents)
|
||||
|
||||
// Post-purchase lifecycle (the grain already supports these transitions).
|
||||
// Post-purchase lifecycle (the grain already supports these transitions).
|
||||
mux.HandleFunc("POST /api/orders/{id}/fulfillments", s.handleFulfill)
|
||||
mux.HandleFunc("POST /api/orders/{id}/complete", s.handleComplete)
|
||||
mux.HandleFunc("POST /api/orders/{id}/cancel", s.handleCancel)
|
||||
mux.HandleFunc("POST /api/orders/{id}/returns", s.handleReturn)
|
||||
mux.HandleFunc("POST /api/orders/{id}/refunds", s.handleRefund)
|
||||
mux.HandleFunc("POST /api/orders/{id}/exchanges", s.handleExchange)
|
||||
mux.HandleFunc("POST /api/orders/{id}/edit", s.handleEditDetails)
|
||||
|
||||
// UCP REST adapter — Universal Commerce Protocol order lifecycle.
|
||||
orderUCP := ucp.OrderHandler(s.applier)
|
||||
@@ -183,10 +189,9 @@ func main() {
|
||||
mux.HandleFunc("GET /sagas/flows/{name}", s.handleGetFlow)
|
||||
mux.HandleFunc("PUT /sagas/flows/{name}", s.handleSaveFlow)
|
||||
|
||||
// Ingest legacy checkout orders (Klarna/Adyen → order-queue) so this service
|
||||
// is the single source of orders, superseding go-order-manager.
|
||||
// Ingest checkout orders from order-queue (Klarna/Adyen fallback).
|
||||
if amqpURL != "" {
|
||||
startLegacyIngest(context.Background(), amqpURL, applier, logger)
|
||||
startOrderIngest(context.Background(), amqpURL, s)
|
||||
}
|
||||
|
||||
logger.Info("order service listening", "addr", addr, "data", dataDir, "flows", flowsDir)
|
||||
@@ -292,7 +297,7 @@ func (s *server) handleCheckout(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
po := buildPlaceOrder(id, &req)
|
||||
po := s.buildPlaceOrder(id, &req)
|
||||
|
||||
st := flow.NewState(uint64(id), s.logger)
|
||||
st.Vars[order.PlaceOrderVar] = po
|
||||
@@ -320,7 +325,8 @@ func (s *server) handleCheckout(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
// buildPlaceOrder converts a checkout request into the PlaceOrder event,
|
||||
// computing per-line and order totals (inc-vat) from the lines.
|
||||
func buildPlaceOrder(id order.OrderId, req *checkoutReq) *messages.PlaceOrder {
|
||||
// Tax amounts are computed using the configured TaxProvider.
|
||||
func (s *server) buildPlaceOrder(id order.OrderId, req *checkoutReq) *messages.PlaceOrder {
|
||||
po := &messages.PlaceOrder{
|
||||
OrderReference: req.OrderReference,
|
||||
CartId: req.CartId,
|
||||
@@ -336,11 +342,7 @@ func buildPlaceOrder(id order.OrderId, req *checkoutReq) *messages.PlaceOrder {
|
||||
var total, totalTax int64
|
||||
for _, l := range req.Lines {
|
||||
lineTotal := l.UnitPrice * int64(l.Quantity)
|
||||
var lineTax int64
|
||||
if l.TaxRate > 0 {
|
||||
// inc-vat -> tax portion = total * rate / (100 + rate)
|
||||
lineTax = lineTotal * int64(l.TaxRate) / int64(100+l.TaxRate)
|
||||
}
|
||||
lineTax := s.taxProvider.ComputeTax(lineTotal, int(l.TaxRate))
|
||||
total += lineTotal
|
||||
totalTax += lineTax
|
||||
po.Lines = append(po.Lines, &messages.OrderLine{
|
||||
@@ -450,10 +452,27 @@ func (s *server) handleList(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
// --- helpers --------------------------------------------------------------
|
||||
|
||||
// selectTaxProvider picks the tax provider from the environment. Default is
|
||||
// NordicTaxProvider with SE as the default country (matching the current
|
||||
// Nordic-only footprint). Set TAX_PROVIDER=static for dev and tests
|
||||
// (no country awareness).
|
||||
func selectTaxProvider() order.TaxProvider {
|
||||
provider := os.Getenv("TAX_PROVIDER")
|
||||
switch provider {
|
||||
case "static":
|
||||
return order.NewStaticTaxProvider()
|
||||
case "nordic":
|
||||
return order.NewNordicTaxProvider(envOr("TAX_DEFAULT_COUNTRY", "SE"))
|
||||
default:
|
||||
return order.NewNordicTaxProvider(envOr("TAX_DEFAULT_COUNTRY", "SE"))
|
||||
}
|
||||
}
|
||||
|
||||
// selectProvider picks the payment provider from the environment. Default is
|
||||
// the dependency-free mock; set PAYMENT_PROVIDER=stripe + STRIPE_SECRET_KEY for
|
||||
// real Stripe (STRIPE_API_BASE / STRIPE_PAYMENT_METHOD optionally override).
|
||||
func selectProvider(logger *slog.Logger) order.PaymentProvider {
|
||||
|
||||
if os.Getenv("PAYMENT_PROVIDER") == "stripe" {
|
||||
key := os.Getenv("STRIPE_SECRET_KEY")
|
||||
if key == "" {
|
||||
|
||||
Reference in New Issue
Block a user