package order import ( "context" "encoding/json" "fmt" "git.k6n.net/mats/go-cart-actor/pkg/flow" ) // Publisher is the minimal seam the amqp_emit hook needs. cmd/order supplies a // RabbitMQ-backed implementation; tests pass a fake. Keeping it an interface // keeps pkg/order free of an AMQP dependency. type Publisher interface { Publish(exchange, routingKey string, body []byte) error } // RegisterEmitHook registers the "amqp_emit" flow hook, which publishes a flow // event to a message broker — the bridge from a saga step to other services // (loyalty, ERP sync, notifications) without coupling the flow to them. It is // the commerce analogue of the CMS event listeners. Params (optional): // // {"exchange": "", "routingKey": "order-events"} // // The body is {step, phase, id, error, vars}. With a nil publisher the hook is // still registered (so the editor lists it) but errors at run time — and since // hook errors never abort a flow, that failure is logged, not fatal. func RegisterEmitHook(reg *flow.Registry, pub Publisher) { reg.Hook("amqp_emit", func(_ context.Context, st *flow.State, info flow.HookInfo, params json.RawMessage) error { if pub == nil { return fmt.Errorf("amqp_emit: no publisher configured") } var p struct { Exchange string `json:"exchange"` RoutingKey string `json:"routingKey"` } if len(params) > 0 { if err := json.Unmarshal(params, &p); err != nil { return fmt.Errorf("amqp_emit: bad params: %w", err) } } if p.RoutingKey == "" { p.RoutingKey = "order-events" } errStr := "" if info.Err != nil { errStr = info.Err.Error() } body, _ := json.Marshal(map[string]any{ "step": info.Step, "phase": info.Phase, "id": st.ID, "error": errStr, "vars": st.Vars, }) return pub.Publish(p.Exchange, p.RoutingKey, body) }) }