package handlers import ( "encoding/json" "gestion/db" "gestion/services" "io" "log" "net/http" "strconv" "github.com/gin-gonic/gin" ) // IPNWebhook - POST /api/v1/webhooks/nowpayments // Reçoit les callbacks de NowPayments lors des changements de statut func IPNWebhook(c *gin.Context) { database := c.MustGet("database").(*db.Database) np, ok := c.MustGet("nowpayments").(*services.NowPaymentsClient) if !ok || np == nil { c.JSON(http.StatusServiceUnavailable, gin.H{"error": "paiement crypto non configuré"}) return } body, err := io.ReadAll(c.Request.Body) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "impossible de lire le corps"}) return } sig := c.GetHeader("x-nowpayments-sig") if !np.VerifyIPN(body, sig) { log.Printf("[IPN] signature invalide") c.JSON(http.StatusUnauthorized, gin.H{"error": "signature invalide"}) return } var payload services.IPNPayload if err := json.Unmarshal(body, &payload); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "payload invalide"}) return } payment, err := database.GetCryptoPaymentByNowPaymentID(payload.PaymentID.String()) if err != nil || payment == nil { log.Printf("[IPN] paiement introuvable: %s", payload.PaymentID.String()) // 200 pour éviter les retries NowPayments c.JSON(http.StatusOK, gin.H{"ok": true}) return } payAmount, _ := payload.PayAmount.Float64() if err := database.UpdateCryptoPaymentStatus(payment.ID, payload.PaymentStatus, payAmount); err != nil { log.Printf("[IPN] erreur mise à jour paiement %d: %v", payment.ID, err) c.JSON(http.StatusInternalServerError, gin.H{"error": "erreur base de données"}) return } switch payload.PaymentStatus { case "finished", "confirmed": if err := database.ActivateCryptoCommand(payment.CommandID); err != nil { log.Printf("[IPN] erreur activation commande %d: %v", payment.CommandID, err) } else { log.Printf("[IPN] commande %d activée (paiement %s confirmé)", payment.CommandID, payload.PaymentID.String()) } case "failed", "expired": if err := database.CancelCryptoCommand(payment.CommandID); err != nil { log.Printf("[IPN] erreur annulation commande %d: %v", payment.CommandID, err) } else { log.Printf("[IPN] commande %d annulée (paiement %s)", payment.CommandID, payload.PaymentStatus) } } c.JSON(http.StatusOK, gin.H{"ok": true}) } // GetCommandPaymentStatus - GET /api/v1/commands/:id/payment-status // Retourne le statut du paiement crypto d'une commande (polling côté client) func GetCommandPaymentStatus(c *gin.Context) { database := c.MustGet("database").(*db.Database) commandID, err := strconv.Atoi(c.Param("id")) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "id de commande invalide"}) return } payment, err := database.GetCryptoPaymentByCommandID(commandID) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "erreur base de données"}) return } if payment == nil { c.JSON(http.StatusNotFound, gin.H{"error": "aucun paiement crypto pour cette commande"}) return } c.JSON(http.StatusOK, gin.H{ "command_id": payment.CommandID, "payment_status": payment.Status, "pay_address": payment.PayAddress, "pay_amount": payment.PayAmount, "pay_currency": payment.PayCurrency, "price_amount": payment.PriceAmount, "price_currency": payment.PriceCurrency, }) }