feat(backend): implement stripe and asaas webhook handlers

This commit is contained in:
Tiago Yamamoto 2025-12-27 01:21:25 -03:00
parent 132fef816c
commit b63242b8fd
2 changed files with 117 additions and 1 deletions

View file

@ -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"})
}

View file

@ -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))