Documentation
¶
Overview ¶
Package v2 implements the V2 order orchestration subdomain of the billing module: idempotent create-and-checkout, guest checkout capabilities, the SSE event-stream tickets and the event stream itself. Only the module facade may reach it.
Index ¶
- Variables
- type Broker
- type Deps
- type EventReader
- type EventSink
- type GuestReplayLimits
- type Orders
- type RedisBroker
- type RedisLimiter
- type ReplayLimiter
- type Service
- func (s *Service) AuthorizeEventStream(ctx context.Context, orderNo, ticket string) (dto.V2OrderSnapshot, time.Time, error)
- func (s *Service) Checkout(ctx context.Context, orderNo string, req *dto.V2CheckoutOrderRequest) (*dto.V2OrderResponse, error)
- func (s *Service) CreateAndCheckout(ctx context.Context, req *dto.V2CreateOrderRequest, idempotencyKey string) (*dto.V2OrderResponse, error)
- func (s *Service) EventTicket(ctx context.Context, orderNo, checkoutToken string) (*dto.V2EventTicketResponse, error)
- func (s *Service) GetOrder(ctx context.Context, orderNo, checkoutToken string) (*dto.V2OrderResponse, error)
- func (s *Service) Session(ctx context.Context, orderNo, checkoutToken string) (*dto.V2OrderSessionResponse, error)
- func (s *Service) StreamEvents(ctx context.Context, req StreamRequest, sink EventSink) error
- type StreamDeps
- type StreamLimiter
- type StreamRequest
- type StreamTiming
- type Subscription
Constants ¶
This section is empty.
Variables ¶
var ErrIdempotencyKeyReused = errors.New("idempotency key reused with a different request")
ErrIdempotencyKeyReused is handled as HTTP 409 by the V2 handler. It is a distinct transport condition: the original order remains intact.
var ErrTooManyStreams = errors.New("too many concurrent event streams")
ErrTooManyStreams reports that the ticket already holds the maximum number of concurrent event streams.
Functions ¶
This section is empty.
Types ¶
type Broker ¶ added in v1.21.0
type Broker interface {
Subscribe(ctx context.Context, orderNo string) (Subscription, error)
}
Broker subscribes to the publication of an order's events.
type Deps ¶
type Deps struct {
Orders Orders
Checkout *checkout.Service
Portal *portal.Service
JwtSecret string
CurrencyUnit func() string
// GuestReplays bounds the replays of anonymous create requests.
GuestReplays GuestReplayLimits
// Stream serves the order event streams.
Stream StreamDeps
}
Deps declares the orchestration's dependencies: sibling subdomains are invoked directly, never through the facade.
type EventReader ¶ added in v1.21.0
type EventReader interface {
ListAfter(ctx context.Context, orderNo string, afterID int64, limit int) ([]*order.Event, error)
EarliestID(ctx context.Context, orderNo string) (int64, error)
}
EventReader reads an order's durable events.
type EventSink ¶ added in v1.21.0
EventSink receives the events of one stream, in order. The first call starts the stream; nothing is sent to the sink when the stream is refused.
type GuestReplayLimits ¶ added in v1.21.0
type GuestReplayLimits struct {
PerKey ReplayLimiter
PerIP ReplayLimiter
}
GuestReplayLimits bound how often an anonymous create request may be replayed under an existing idempotency key. A replay proves the guest password against the order, so an unlimited replay is a password oracle for whoever holds the key; the limits apply per key and per client IP. A nil limiter applies no limit.
type Orders ¶ added in v1.21.0
type Orders interface {
FindOneByOrderNo(ctx context.Context, orderNo string) (*order.Order, error)
FindOneByIdempotencyKey(ctx context.Context, key string) (*order.Order, error)
}
Orders reads the orders the orchestration creates.
type RedisBroker ¶ added in v1.21.0
RedisBroker wakes streams through the Redis channel the order event publisher broadcasts on.
func (RedisBroker) Subscribe ¶ added in v1.21.0
func (b RedisBroker) Subscribe(ctx context.Context, orderNo string) (Subscription, error)
Subscribe confirms the subscription before returning it, so a stream only counts on wake-ups Redis actually delivers.
type RedisLimiter ¶ added in v1.21.0
RedisLimiter counts a ticket's streams in Redis. During a Redis outage every stream is admitted: the event table can still sustain it, and the cap is a best-effort abuse control, not a reason to hide a paid order from its owner.
type ReplayLimiter ¶ added in v1.21.0
ReplayLimiter grants a bounded number of permits per key and period; the auth rate limiter provides it, with its permit states.
type Service ¶
type Service struct {
// contains filtered or unexported fields
}
Service is the V2 orchestration.
func NewService ¶
func (*Service) AuthorizeEventStream ¶
func (s *Service) AuthorizeEventStream(ctx context.Context, orderNo, ticket string) (dto.V2OrderSnapshot, time.Time, error)
AuthorizeEventStream validates the self-contained stream capability and returns the stream's initial snapshot together with the ticket expiry; the order entity never leaves the module.
func (*Service) Checkout ¶
func (s *Service) Checkout(ctx context.Context, orderNo string, req *dto.V2CheckoutOrderRequest) (*dto.V2OrderResponse, error)
Checkout resumes the payment of a pending order.
func (*Service) CreateAndCheckout ¶
func (s *Service) CreateAndCheckout(ctx context.Context, req *dto.V2CreateOrderRequest, idempotencyKey string) (*dto.V2OrderResponse, error)
CreateAndCheckout is the V2 orchestration boundary. Existing domain creators still own pricing, inventory and coupon policy; this method only gives them an idempotency context and immediately starts checkout.
func (*Service) EventTicket ¶
func (s *Service) EventTicket(ctx context.Context, orderNo, checkoutToken string) (*dto.V2EventTicketResponse, error)
EventTicket mints a fresh stream ticket for the order.
func (*Service) GetOrder ¶
func (s *Service) GetOrder(ctx context.Context, orderNo, checkoutToken string) (*dto.V2OrderResponse, error)
GetOrder returns the order's state snapshot with a fresh stream ticket.
func (*Service) Session ¶
func (s *Service) Session(ctx context.Context, orderNo, checkoutToken string) (*dto.V2OrderSessionResponse, error)
Session exchanges the durable guest checkout capability for an ordinary session after activation has created the account, within the window and under the revocation rule the storefront applies. It is intentionally a separate JSON endpoint: a long-lived access token must never appear in a browser-visible EventSource URL or SSE event payload.
func (*Service) StreamEvents ¶ added in v1.21.0
StreamEvents serves one order event stream until ctx ends or the ticket expires. The order is authorization, subscribe, replay, then live events: subscribing before the replay query closes the window in which a payment between the query and the subscription would be missed, and the event id makes the overlap of both safe to deliver twice.
type StreamDeps ¶ added in v1.21.0
type StreamDeps struct {
Events EventReader
Broker Broker
Limiter StreamLimiter
Timing StreamTiming
}
StreamDeps serves the order event streams. The event table is the source of truth; the broker only wakes a stream quickly after an outbox publication, and a periodic catch-up keeps streams correct while the broker or a subscription is unavailable.
type StreamLimiter ¶ added in v1.21.0
type StreamLimiter interface {
// Acquire takes one of the ticket's stream slots for at most ttl and
// returns its release.
Acquire(ctx context.Context, ticket string, ttl time.Duration) (release func(), ok bool)
}
StreamLimiter bounds the concurrent streams of one ticket.
type StreamRequest ¶ added in v1.21.0
StreamRequest names the stream: the order, its ticket and the replay cursor (Last-Event-ID, or the after query parameter).
type StreamTiming ¶ added in v1.21.0
type StreamTiming struct {
Heartbeat time.Duration
CatchUp time.Duration
Resubscribe time.Duration
}
StreamTiming paces a stream; zero values select the production pacing.
type Subscription ¶ added in v1.21.0
type Subscription interface {
Wake() <-chan struct{}
Close() error
}
Subscription wakes a stream when an order's events were published. Its channel closes when the subscription ends.