omnix-sopiga/handler/webhook.go

83 lines
2.3 KiB
Go
Raw Normal View History

package handler
import (
"crypto/hmac"
"crypto/sha256"
"crypto/subtle"
"encoding/hex"
"encoding/json"
"io"
"net/http"
fwlogger "repository.promas.id/prana/go-dw-framework/logger"
"repository.promas.id/prana/omnix-sopiga/dto"
"repository.promas.id/prana/omnix-sopiga/service"
)
const signatureHeader = "X-Sopiga-Signature"
// WebhookHandler receives delivery-status callbacks pushed by Omnix,
// as an alternative to polling. Payload shape confirmed by the Omnix team
// (see dto.DeliveryStatusWebhook); the signature scheme is still our own
// proposal pending their confirmation.
type WebhookHandler struct {
service *service.Service
logger *fwlogger.Logger
secret string
}
func NewWebhookHandler(svc *service.Service, log *fwlogger.Logger, secret string) *WebhookHandler {
return &WebhookHandler{service: svc, logger: log, secret: secret}
}
func (h *WebhookHandler) HandleDeliveryStatus(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
w.WriteHeader(http.StatusMethodNotAllowed)
return
}
body, err := io.ReadAll(r.Body)
if err != nil {
h.logger.Error("webhook: read body failed", "error", err)
w.WriteHeader(http.StatusBadRequest)
return
}
defer r.Body.Close()
if h.secret != "" && !h.verifySignature(body, r.Header.Get(signatureHeader)) {
h.logger.Error("webhook: signature verification failed")
w.WriteHeader(http.StatusUnauthorized)
return
}
var payload dto.DeliveryStatusWebhook
if err := json.Unmarshal(body, &payload); err != nil {
h.logger.Error("webhook: invalid payload", "error", err)
w.WriteHeader(http.StatusBadRequest)
return
}
if err := h.service.SyncDeliveryFromWebhook(r.Context(), payload); err != nil {
h.logger.Error("webhook: sync delivery failed",
"recipient_detail_id", payload.RecipientDetailID,
"error", err)
w.WriteHeader(http.StatusInternalServerError)
return
}
h.logger.Info("webhook: delivery status applied",
"recipient_detail_id", payload.RecipientDetailID,
"status", payload.Status,
"gateway", payload.Gateway)
w.WriteHeader(http.StatusOK)
}
func (h *WebhookHandler) verifySignature(body []byte, signature string) bool {
mac := hmac.New(sha256.New, []byte(h.secret))
mac.Write(body)
expected := hex.EncodeToString(mac.Sum(nil))
return subtle.ConstantTimeCompare([]byte(expected), []byte(signature)) == 1
}