feat(backend): implement stripe and asaas webhook handlers
This commit is contained in:
parent
132fef816c
commit
b63242b8fd
2 changed files with 117 additions and 1 deletions
|
|
@ -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"})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
|
|
|
|||
Loading…
Reference in a new issue