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 func IPNWebhook(c *gin.Context) { database := c.MustGet("database").(*db.Database) npRaw, npExists := c.Get("nowpayments") np, ok := npRaw.(*services.NowPaymentsClient) if !npExists || !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()) 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.StatusBadRequest, 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) // Effectue un refresh temps réel depuis NowPayments si le paiement est encore en attente 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 } // Refresh temps réel depuis NowPayments pour les statuts intermédiaires (comme la référence) switch payment.Status { case "waiting", "confirming", "confirmed", "sending": np, npOk := c.Get("nowpayments") if npOk && np != nil { npClient := np.(*services.NowPaymentsClient) npStatus, err := npClient.GetPaymentStatus(payment.NowPaymentID) if err == nil && npStatus.PaymentStatus != payment.Status { payAmount, _ := npStatus.PayAmount.Float64() if updateErr := database.UpdateCryptoPaymentStatus(payment.ID, npStatus.PaymentStatus, payAmount); updateErr == nil { payment.Status = npStatus.PaymentStatus payment.PayAmount = payAmount log.Printf("[PAYMENT-STATUS] cmd %d: %s → %s (refresh temps réel)", commandID, payment.Status, npStatus.PaymentStatus) } switch npStatus.PaymentStatus { case "finished", "confirmed": _ = database.ActivateCryptoCommand(commandID) case "failed", "expired": _ = database.CancelCryptoCommand(commandID) } } } } 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, }) }