diff --git a/backend/internal/http/handler/payment_handler.go b/backend/internal/http/handler/payment_handler.go index 22eba4c..115b7b0 100644 --- a/backend/internal/http/handler/payment_handler.go +++ b/backend/internal/http/handler/payment_handler.go @@ -4,6 +4,7 @@ import ( "net/http" "strings" + "github.com/gofrs/uuid/v5" "github.com/saveinmed/backend-go/internal/domain" ) @@ -114,3 +115,116 @@ func (h *Handler) HandlePaymentWebhook(w http.ResponseWriter, r *http.Request) { writeJSON(w, http.StatusOK, summary) } + +// HandleStripeWebhook godoc +// @Summary Recebe notificações do Stripe +// @Tags Pagamentos +// @Accept json +// @Produce json +// @Success 200 {object} map[string]string +// @Router /api/v1/payments/webhook/stripe [post] +func (h *Handler) HandleStripeWebhook(w http.ResponseWriter, r *http.Request) { + // In production: Verify Stripe-Signature header using the raw body + // signature := r.Header.Get("Stripe-Signature") + + var payload struct { + Type string `json:"type"` + Data struct { + Object struct { + ID string `json:"id"` + Metadata struct { + OrderID string `json:"order_id"` + } `json:"metadata"` + Status string `json:"status"` + AmountReceived int64 `json:"amount_received"` + ApplicationFee int64 `json:"application_fee_amount"` + TransferData struct { + Amount int64 `json:"amount"` // Seller amount + } `json:"transfer_data"` + } `json:"object"` + } `json:"data"` + } + + if err := decodeJSON(r.Context(), r, &payload); err != nil { + writeError(w, http.StatusBadRequest, err) + return + } + + // Map Stripe event to generic domain event + if payload.Type == "payment_intent.succeeded" { + orderID, _ := uuid.FromString(payload.Data.Object.Metadata.OrderID) + event := domain.PaymentWebhookEvent{ + PaymentID: payload.Data.Object.ID, + OrderID: orderID, + Status: "approved", // Stripe succeeded = approved + TotalPaidAmount: payload.Data.Object.AmountReceived, + MarketplaceFee: payload.Data.Object.ApplicationFee, + SellerAmount: payload.Data.Object.TransferData.Amount, + } + + if _, err := h.svc.HandlePaymentWebhook(r.Context(), event); err != nil { + writeError(w, http.StatusInternalServerError, err) + return + } + } + + writeJSON(w, http.StatusOK, map[string]string{"received": "true"}) +} + +// HandleAsaasWebhook godoc +// @Summary Recebe notificações do Asaas +// @Tags Pagamentos +// @Accept json +// @Produce json +// @Success 200 {object} map[string]string +// @Router /api/v1/payments/webhook/asaas [post] +func (h *Handler) HandleAsaasWebhook(w http.ResponseWriter, r *http.Request) { + // In production: Verify asaas-access-token header + // token := r.Header.Get("asaas-access-token") + + var payload struct { + Event string `json:"event"` + Payment struct { + ID string `json:"id"` + ExternalRef string `json:"externalReference"` // We store OrderID here + Status string `json:"status"` + NetValue int64 `json:"netValue"` // value after fees? Need to check docs. + Value float64 `json:"value"` // Asaas sends float + Split []struct { + WalletID string `json:"walletId"` + FixedValue float64 `json:"fixedValue"` + } `json:"split"` + } `json:"payment"` + } + + if err := decodeJSON(r.Context(), r, &payload); err != nil { + writeError(w, http.StatusBadRequest, err) + return + } + + if payload.Event == "PAYMENT_RECEIVED" || payload.Event == "PAYMENT_CONFIRMED" { + orderID, _ := uuid.FromString(payload.Payment.ExternalRef) + + // Convert Asaas float value to cents + totalCents := int64(payload.Payment.Value * 100) + + // Find split part for marketplace/seller + // This logic depends on how we set up the split. + // For now, simpler mapping: + + event := domain.PaymentWebhookEvent{ + PaymentID: payload.Payment.ID, + OrderID: orderID, + Status: "approved", + TotalPaidAmount: totalCents, + // MarketplaceFee and SellerAmount might need calculation or extraction from split array + } + + if _, err := h.svc.HandlePaymentWebhook(r.Context(), event); err != nil { + writeError(w, http.StatusInternalServerError, err) + return + } + } + + writeJSON(w, http.StatusOK, map[string]string{"received": "true"}) +} diff --git a/backend/internal/server/server.go b/backend/internal/server/server.go index 8ecf940..76f6bbc 100644 --- a/backend/internal/server/server.go +++ b/backend/internal/server/server.go @@ -93,7 +93,9 @@ func New(cfg config.Config) (*Server, error) { mux.Handle("GET /api/v1/shipments", chain(http.HandlerFunc(h.ListShipments), middleware.Logger, middleware.Gzip, auth)) mux.Handle("GET /api/v1/shipments/", chain(http.HandlerFunc(h.GetShipmentByOrderID), middleware.Logger, middleware.Gzip, auth)) - mux.Handle("POST /api/v1/payments/webhook", chain(http.HandlerFunc(h.HandlePaymentWebhook), middleware.Logger, middleware.Gzip)) + mux.Handle("POST /api/v1/payments/webhook", chain(http.HandlerFunc(h.HandlePaymentWebhook), middleware.Logger, middleware.Gzip)) // Generic/Mercado Pago + mux.Handle("POST /api/v1/payments/webhook/stripe", chain(http.HandlerFunc(h.HandleStripeWebhook), middleware.Logger, middleware.Gzip)) + mux.Handle("POST /api/v1/payments/webhook/asaas", chain(http.HandlerFunc(h.HandleAsaasWebhook), middleware.Logger, middleware.Gzip)) mux.Handle("POST /api/v1/reviews", chain(http.HandlerFunc(h.CreateReview), middleware.Logger, middleware.Gzip, auth)) mux.Handle("GET /api/v1/reviews", chain(http.HandlerFunc(h.ListReviews), middleware.Logger, middleware.Gzip, auth))