all the refactor
This commit is contained in:
+80
-10
@@ -66,9 +66,33 @@ func (s *server) loadOrder(w http.ResponseWriter, r *http.Request) (order.OrderI
|
||||
return id, g, true
|
||||
}
|
||||
|
||||
// checkIdempotency checks if the request is an idempotent retry.
|
||||
// It returns (idemKey, exists, unlockFn).
|
||||
// If exists is true, the caller should write the current order grain status and return immediately.
|
||||
// If exists is false, the caller must defer unlockFn() and continue.
|
||||
func (s *server) checkIdempotency(w http.ResponseWriter, r *http.Request, id order.OrderId) (string, bool, func()) {
|
||||
idemKey := r.Header.Get("Idempotency-Key")
|
||||
if idemKey == "" {
|
||||
return "", false, func() {}
|
||||
}
|
||||
unlock := s.idem.Lock(idemKey)
|
||||
if _, ok := s.idem.Get(idemKey); ok {
|
||||
unlock() // Release lock immediately since we are returning cached response
|
||||
s.logger.Info("idempotency hit for lifecycle endpoint", "key", idemKey, "orderId", id.String())
|
||||
grain, err := s.applier.Get(r.Context(), uint64(id))
|
||||
if err != nil {
|
||||
writeErr(w, http.StatusInternalServerError, err)
|
||||
return idemKey, true, nil
|
||||
}
|
||||
writeJSON(w, http.StatusOK, map[string]any{"orderId": id.String(), "order": grain})
|
||||
return idemKey, true, nil
|
||||
}
|
||||
return idemKey, false, unlock
|
||||
}
|
||||
|
||||
// applyAndRespond applies a mutation and returns the updated order. A rejected
|
||||
// state transition (handler error) maps to 409 Conflict.
|
||||
func (s *server) applyAndRespond(w http.ResponseWriter, r *http.Request, id order.OrderId, msg proto.Message) {
|
||||
func (s *server) applyAndRespond(w http.ResponseWriter, r *http.Request, id order.OrderId, msg proto.Message, idemKey string) {
|
||||
res, err := s.applier.Apply(r.Context(), uint64(id), msg)
|
||||
if err != nil {
|
||||
writeErr(w, http.StatusInternalServerError, err)
|
||||
@@ -80,6 +104,11 @@ func (s *server) applyAndRespond(w http.ResponseWriter, r *http.Request, id orde
|
||||
return
|
||||
}
|
||||
}
|
||||
if idemKey != "" {
|
||||
if err := s.idem.Put(idemKey, uint64(id)); err != nil {
|
||||
s.logger.Error("idempotency record failed", "key", idemKey, "orderId", id.String(), "err", err)
|
||||
}
|
||||
}
|
||||
grain, err := s.applier.Get(r.Context(), uint64(id))
|
||||
if err != nil {
|
||||
writeErr(w, http.StatusInternalServerError, err)
|
||||
@@ -105,6 +134,12 @@ func (s *server) handleFulfill(w http.ResponseWriter, r *http.Request) {
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
idemKey, exists, unlock := s.checkIdempotency(w, r, id)
|
||||
if exists {
|
||||
return
|
||||
}
|
||||
defer unlock()
|
||||
|
||||
var req fulfillReq
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
writeErr(w, http.StatusBadRequest, err)
|
||||
@@ -182,7 +217,7 @@ func (s *server) handleFulfill(w http.ResponseWriter, r *http.Request) {
|
||||
for _, l := range req.Lines {
|
||||
msg.Lines = append(msg.Lines, &messages.FulfillmentLine{Reference: l.Reference, Quantity: l.Quantity})
|
||||
}
|
||||
s.applyAndRespond(w, r, id, msg)
|
||||
s.applyAndRespond(w, r, id, msg, idemKey)
|
||||
}
|
||||
|
||||
type exchangeReq struct {
|
||||
@@ -208,6 +243,12 @@ func (s *server) handleExchange(w http.ResponseWriter, r *http.Request) {
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
idemKey, exists, unlock := s.checkIdempotency(w, r, id)
|
||||
if exists {
|
||||
return
|
||||
}
|
||||
defer unlock()
|
||||
|
||||
var req exchangeReq
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
writeErr(w, http.StatusBadRequest, err)
|
||||
@@ -236,7 +277,7 @@ func (s *server) handleExchange(w http.ResponseWriter, r *http.Request) {
|
||||
TotalTax: l.TotalTax,
|
||||
})
|
||||
}
|
||||
s.applyAndRespond(w, r, id, msg)
|
||||
s.applyAndRespond(w, r, id, msg, idemKey)
|
||||
}
|
||||
|
||||
type editDetailsReq struct {
|
||||
@@ -250,6 +291,12 @@ func (s *server) handleEditDetails(w http.ResponseWriter, r *http.Request) {
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
idemKey, exists, unlock := s.checkIdempotency(w, r, id)
|
||||
if exists {
|
||||
return
|
||||
}
|
||||
defer unlock()
|
||||
|
||||
var req editDetailsReq
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
writeErr(w, http.StatusBadRequest, err)
|
||||
@@ -261,16 +308,21 @@ func (s *server) handleEditDetails(w http.ResponseWriter, r *http.Request) {
|
||||
ShippingPrice: req.ShippingPrice,
|
||||
AtMs: nowMs(),
|
||||
}
|
||||
s.applyAndRespond(w, r, id, msg)
|
||||
s.applyAndRespond(w, r, id, msg, idemKey)
|
||||
}
|
||||
|
||||
|
||||
func (s *server) handleComplete(w http.ResponseWriter, r *http.Request) {
|
||||
id, _, ok := s.loadOrder(w, r)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
s.applyAndRespond(w, r, id, &messages.CompleteOrder{AtMs: nowMs()})
|
||||
idemKey, exists, unlock := s.checkIdempotency(w, r, id)
|
||||
if exists {
|
||||
return
|
||||
}
|
||||
defer unlock()
|
||||
|
||||
s.applyAndRespond(w, r, id, &messages.CompleteOrder{AtMs: nowMs()}, idemKey)
|
||||
}
|
||||
|
||||
func (s *server) handleCancel(w http.ResponseWriter, r *http.Request) {
|
||||
@@ -278,11 +330,17 @@ func (s *server) handleCancel(w http.ResponseWriter, r *http.Request) {
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
idemKey, exists, unlock := s.checkIdempotency(w, r, id)
|
||||
if exists {
|
||||
return
|
||||
}
|
||||
defer unlock()
|
||||
|
||||
var req struct {
|
||||
Reason string `json:"reason"`
|
||||
}
|
||||
_ = json.NewDecoder(r.Body).Decode(&req)
|
||||
s.applyAndRespond(w, r, id, &messages.CancelOrder{Reason: req.Reason, AtMs: nowMs()})
|
||||
s.applyAndRespond(w, r, id, &messages.CancelOrder{Reason: req.Reason, AtMs: nowMs()}, idemKey)
|
||||
}
|
||||
|
||||
type returnReq struct {
|
||||
@@ -298,6 +356,12 @@ func (s *server) handleReturn(w http.ResponseWriter, r *http.Request) {
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
idemKey, exists, unlock := s.checkIdempotency(w, r, id)
|
||||
if exists {
|
||||
return
|
||||
}
|
||||
defer unlock()
|
||||
|
||||
var req returnReq
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
writeErr(w, http.StatusBadRequest, err)
|
||||
@@ -308,7 +372,7 @@ func (s *server) handleReturn(w http.ResponseWriter, r *http.Request) {
|
||||
for _, l := range req.Lines {
|
||||
msg.Lines = append(msg.Lines, &messages.FulfillmentLine{Reference: l.Reference, Quantity: l.Quantity})
|
||||
}
|
||||
s.applyAndRespond(w, r, id, msg)
|
||||
s.applyAndRespond(w, r, id, msg, idemKey)
|
||||
}
|
||||
|
||||
func (s *server) handleRefund(w http.ResponseWriter, r *http.Request) {
|
||||
@@ -316,13 +380,19 @@ func (s *server) handleRefund(w http.ResponseWriter, r *http.Request) {
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
idemKey, exists, unlock := s.checkIdempotency(w, r, id)
|
||||
if exists {
|
||||
return
|
||||
}
|
||||
defer unlock()
|
||||
|
||||
var req struct {
|
||||
Amount int64 `json:"amount"`
|
||||
}
|
||||
_ = json.NewDecoder(r.Body).Decode(&req)
|
||||
amount := req.Amount
|
||||
if amount <= 0 {
|
||||
amount = g.CapturedAmount - g.RefundedAmount // default: full remaining
|
||||
amount = (g.CapturedAmount - g.RefundedAmount).Int64() // default: full remaining
|
||||
}
|
||||
captureRef := capturedRef(g)
|
||||
ref, err := s.provider.Refund(r.Context(), captureRef, amount)
|
||||
@@ -335,7 +405,7 @@ func (s *server) handleRefund(w http.ResponseWriter, r *http.Request) {
|
||||
Amount: ref.Amount,
|
||||
Reference: ref.Reference,
|
||||
AtMs: nowMs(),
|
||||
})
|
||||
}, idemKey)
|
||||
}
|
||||
|
||||
// --- saga / flow admin (a) ------------------------------------------------
|
||||
|
||||
Reference in New Issue
Block a user