79 lines
2.5 KiB
Go
79 lines
2.5 KiB
Go
package order
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"time"
|
|
|
|
"git.k6n.net/mats/go-cart-actor/pkg/flow"
|
|
"git.k6n.net/mats/platform/event"
|
|
contract "git.k6n.net/mats/platform/order"
|
|
)
|
|
|
|
// RegisterOrderCreatedEmit registers the "emit_order_created" flow hook: it
|
|
// publishes an event.OrderCreated envelope (payload contract.Created) for the
|
|
// order, reading the grain via app and publishing durably via pub.
|
|
//
|
|
// It is a HOOK, not an action, on purpose: hook errors are logged, never abort
|
|
// the flow. The order is already placed and paid by the time this fires, so a
|
|
// transient publish hiccup must not roll it back — and pub is the durable outbox
|
|
// (a local fsync'd append), so "failure" here means disk error, then the relay
|
|
// retries delivery on its own. With a nil publisher (dev without AMQP) it is a
|
|
// no-op. Attach it to the "after" hooks of the final paid step, e.g. capture.
|
|
func RegisterOrderCreatedEmit(reg *flow.Registry, app Applier, pub Publisher, exchange, source string) {
|
|
reg.Hook("emit_order_created", func(ctx context.Context, st *flow.State, _ flow.HookInfo, _ json.RawMessage) error {
|
|
if pub == nil {
|
|
return nil // no broker configured (dev)
|
|
}
|
|
o, err := app.Get(ctx, st.ID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
created := buildOrderCreated(o)
|
|
if len(created.Lines) == 0 {
|
|
return nil // nothing for a reactor to act on
|
|
}
|
|
payload, err := created.Encode()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
ev := event.Event{
|
|
ID: "order-created-" + created.OrderID,
|
|
Type: event.OrderCreated,
|
|
Source: source,
|
|
Subject: created.OrderID,
|
|
Payload: payload,
|
|
Time: time.Now(),
|
|
Meta: map[string]string{"country": created.Country},
|
|
}
|
|
body, err := json.Marshal(ev)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return pub.Publish(exchange, string(event.OrderCreated), body)
|
|
})
|
|
}
|
|
|
|
// buildOrderCreated projects the grain onto the exported order.created contract.
|
|
// Lines carry only SKU + quantity (what reactors need); inventory commits at the
|
|
// order's Country location.
|
|
func buildOrderCreated(o *OrderGrain) contract.Created {
|
|
lines := make([]contract.Line, 0, len(o.Lines))
|
|
for _, l := range o.Lines {
|
|
if l.Sku == "" || l.Quantity <= 0 {
|
|
continue
|
|
}
|
|
lines = append(lines, contract.Line{SKU: l.Sku, Quantity: l.Quantity, Location: l.Location})
|
|
}
|
|
return contract.Created{
|
|
OrderID: OrderId(o.Id).String(),
|
|
OrderReference: o.OrderReference,
|
|
CartID: o.CartId,
|
|
Country: o.Country,
|
|
Currency: o.Currency,
|
|
CustomerEmail: o.CustomerEmail,
|
|
Lines: lines,
|
|
PlacedAtMs: nowMs(),
|
|
}
|
|
}
|