Parcourir la source

update onboard api

lblt il y a 1 mois
Parent
commit
69b2c61c30

+ 44 - 0
Dockerfile

@@ -0,0 +1,44 @@
+# Build stage
+FROM golang:1.22.5-alpine AS builder
+
+# Install build dependencies
+RUN apk add --no-cache git make gcc musl-dev sqlite-dev
+
+WORKDIR /app
+
+# Copy go mod files
+COPY go.mod go.sum ./
+RUN go mod download
+
+# Copy source code
+COPY . .
+
+# Build the application
+RUN CGO_ENABLED=1 GOOS=linux go build -o byom-onboard ./cmd/api/main.go
+
+# Final stage
+FROM alpine:3.19
+
+# Install runtime dependencies
+RUN apk add --no-cache ca-certificates tzdata sqlite sqlite-dev
+
+# Create non-root user
+RUN adduser -D -H -h /app appuser
+
+WORKDIR /app
+
+# Copy binary from builder
+COPY --from=builder /app/byom-onboard .
+COPY config.yaml .
+
+# Create data directory and set permissions
+RUN mkdir -p /app/data && \
+    chown -R appuser:appuser /app
+
+USER appuser
+
+EXPOSE 8080
+
+ENV CONFIG_FILE=/app/config.yaml
+
+CMD ["./byom-onboard", "serve", "--config=/app/config.yaml"] 

+ 20 - 22
cmd/api/main.go

@@ -15,12 +15,12 @@ import (
 	"github.com/gin-gonic/gin"
 
 	"git.linuxforward.com/byom/byom-onboard/internal/common/jwt"
-	"git.linuxforward.com/byom/byom-onboard/internal/domain/billing"
 	"git.linuxforward.com/byom/byom-onboard/internal/domain/register"
 	"git.linuxforward.com/byom/byom-onboard/internal/platform/config"
 	"git.linuxforward.com/byom/byom-onboard/internal/platform/database"
 	"git.linuxforward.com/byom/byom-onboard/internal/platform/mailer"
 	"git.linuxforward.com/byom/byom-onboard/internal/platform/payment"
+	"git.linuxforward.com/byom/byom-onboard/internal/platform/stripe"
 )
 
 var (
@@ -59,6 +59,11 @@ func (s *ServeCmd) Run(cli *CLI) error {
 		return fmt.Errorf("failed to load configuration: %w", err)
 	}
 
+	// Initialize Stripe
+	if err := stripe.Initialize(); err != nil {
+		log.Fatalf("Failed to initialize Stripe: %v", err)
+	}
+
 	// Initialize Gin
 	gin.SetMode(gin.ReleaseMode)
 	r := gin.New()
@@ -66,7 +71,7 @@ func (s *ServeCmd) Run(cli *CLI) error {
 	r.Use(gin.Recovery())
 
 	corsConfig := cors.DefaultConfig()
-	corsConfig.AllowOrigins = []string{"http://192.168.1.35:5173"}
+	corsConfig.AllowOrigins = []string{"https://byom.moooffle.com"}
 	corsConfig.AllowCredentials = true
 	corsConfig.AllowHeaders = []string{"Content-Type", "Authorization"}
 	corsConfig.AllowMethods = []string{"*"}
@@ -85,30 +90,25 @@ func (s *ServeCmd) Run(cli *CLI) error {
 	}
 
 	jwtClient := jwt.NewJWTClient([]byte(cfg.JWT.Secret))
-
-	// Initialize services
-	stripeClient := billing.NewStripeClient(cfg.Stripe.APIKey, cfg.Stripe.EndpointSecret)
+	//stripeClient := billing.NewStripeClient(cfg.Stripe.APIKey, cfg.Stripe.EndpointSecret)
 	mailerClient := mailer.NewMailer(&cfg.Mailer)
-	//ovhClient := provisioning.NewOvhClient(cfg.OVH.Endpoint, cfg.OVH.AppKey, cfg.OVH.AppSecret)
-
-	// Initialize handlers
-	billingHandler := billing.NewHandler(stripeClient, db)
-	//provisioningHandler := provisioning.NewHandler(ovhClient, db)
-	registerHandler := register.NewHandler(mailerClient, db, jwtClient)
-
 	// Initialize Stripe handler
 	stripeHandler, err := payment.NewStripeHandler(
-		cfg.Stripe.APIKey,
-		"whsec_527fbb8ce7f9072a60a17a37e2807965c08a10fb48aa218e9ed14b7835520844",
-		"http://192.168.1.35:5173",
+		"https://byom.moooffle.com",
 		mailerClient,
 	)
 	if err != nil {
 		return fmt.Errorf("failed to initialize Stripe: %w", err)
 	}
 
+	// Initialize services
+	//ovhClient := provisioning.NewOvhClient(cfg.OVH.Endpoint, cfg.OVH.AppKey, cfg.OVH.AppSecret)
+
+	//provisioningHandler := provisioning.NewHandler(ovhClient, db)
+	registerHandler := register.NewHandler(mailerClient, db, jwtClient)
+
 	// Setup server
-	server := setupServer(r, billingHandler, registerHandler, cfg, stripeHandler)
+	server := setupServer(r, registerHandler, cfg, stripeHandler)
 	server.Addr = fmt.Sprintf(":%d", s.Port)
 
 	// Start server in a goroutine
@@ -138,27 +138,25 @@ func (s *ServeCmd) Run(cli *CLI) error {
 
 func setupServer(
 	r *gin.Engine,
-	billingHandler *billing.Handler,
 	registerHandler *register.Handler,
 	config *config.Config,
 	stripeHandler *payment.StripeHandler,
 ) *http.Server {
 
-	api := r.Group("/api/v1")
+	api := r.Group("/api/v1/onboard")
 	{
 		// Public routes
 		api.POST("/check-email", registerHandler.CheckEmail)
 		api.POST("/register", registerHandler.Register)
 		api.GET("/validate-email", registerHandler.ValidateEmail)
 
-		// Payment routes
-		api.POST("/payment", billingHandler.CreatePayment)
-		api.POST("/payment/webhook", billingHandler.HandleWebhook)
-
 		// Stripe routes
 		api.POST("/create-checkout-session", stripeHandler.CreateCheckoutSession)
 		api.POST("/create-portal-session", stripeHandler.CreatePortalSession)
 		api.POST("/webhook", stripeHandler.HandleWebhook)
+
+		// Add the new products endpoint
+		api.GET("/products", stripeHandler.GetProducts)
 	}
 
 	return &http.Server{

+ 0 - 81
internal/domain/billing/handler.go

@@ -1,81 +0,0 @@
-package billing
-
-import (
-	"io"
-	"net/http"
-
-	"git.linuxforward.com/byom/byom-onboard/internal/common/models"
-	"git.linuxforward.com/byom/byom-onboard/internal/platform/database"
-	"github.com/gin-gonic/gin"
-)
-
-type Handler struct {
-	client BillingClient
-	db     *database.Database
-}
-
-func NewHandler(client BillingClient, db *database.Database) *Handler {
-	return &Handler{
-		client: client,
-		db:     db,
-	}
-}
-
-func (h *Handler) CreatePayment(c *gin.Context) {
-	var req models.PaymentRequest
-	if err := c.ShouldBindJSON(&req); err != nil {
-		c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid request format"})
-		return
-	}
-
-	intent, err := h.client.CreatePaymentIntent(req.Amount, req.Currency, req.PaymentMethodID)
-	if err != nil {
-		c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
-		return
-	}
-
-	c.JSON(http.StatusOK, intent)
-}
-
-func (h *Handler) HandleWebhook(c *gin.Context) {
-	payload, err := io.ReadAll(c.Request.Body)
-	if err != nil {
-		c.JSON(http.StatusBadRequest, gin.H{"error": "Unable to read request body"})
-		return
-	}
-
-	signature := c.GetHeader("Stripe-Signature")
-	if err := h.client.HandleWebhook(payload, signature); err != nil {
-		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
-		return
-	}
-
-	c.JSON(http.StatusOK, gin.H{"received": true})
-}
-
-func (h *Handler) InitiateSubscription(c *gin.Context) {
-	var req struct {
-		SessionToken string `json:"session_token" binding:"required"`
-		PlanID       string `json:"plan_id" binding:"required"`
-	}
-	if err := c.ShouldBindJSON(&req); err != nil {
-		c.JSON(400, gin.H{"error": "Invalid request"})
-		return
-	}
-
-	// Get plan details from DefaultPlans
-	plan, exists := models.GetPlan(req.PlanID)
-	if !exists {
-		c.JSON(400, gin.H{"error": "Invalid plan"})
-		return
-	}
-
-	// Create payment intent with plan price
-	intent, err := h.client.CreatePaymentIntent(plan.Price, plan.Currency, "")
-	if err != nil {
-		c.JSON(500, gin.H{"error": "Failed to create payment"})
-		return
-	}
-
-	c.JSON(200, intent)
-}

+ 0 - 18
internal/domain/billing/service.go

@@ -1,18 +0,0 @@
-package billing
-
-import (
-	"git.linuxforward.com/byom/byom-onboard/internal/common/models"
-)
-
-type BillingClient interface {
-	CreatePaymentIntent(amount int64, currency string, paymentMethodID string) (*models.PaymentIntent, error)
-	HandleWebhook(payload []byte, signature string) error
-}
-
-// TODO: Implement later
-// To check available plan and prices
-// Can also provide dynamic prices
-// type PlanService interface {
-// 	GetPlan(planID string) (*models.Plan, error)
-// 	ListPlans() ([]*models.Plan, error)
-// }

+ 0 - 96
internal/domain/billing/stripe.go

@@ -1,96 +0,0 @@
-package billing
-
-import (
-	"encoding/json"
-	"fmt"
-	"log"
-
-	"git.linuxforward.com/byom/byom-onboard/internal/common/models"
-
-	"github.com/stripe/stripe-go/v81"
-	"github.com/stripe/stripe-go/v81/paymentintent"
-	"github.com/stripe/stripe-go/v81/webhook"
-)
-
-type StripeClient struct {
-	secretKey     string
-	webhookSecret string
-}
-
-func NewStripeClient(secretKey, webhookSecret string) *StripeClient {
-	stripe.Key = secretKey
-	return &StripeClient{
-		secretKey:     secretKey,
-		webhookSecret: webhookSecret,
-	}
-}
-
-func (s *StripeClient) CreatePaymentIntent(amount int64, currency string, paymentMethodID string) (*models.PaymentIntent, error) {
-	params := &stripe.PaymentIntentParams{
-		Amount:   stripe.Int64(amount),
-		Currency: stripe.String(currency),
-		AutomaticPaymentMethods: &stripe.PaymentIntentAutomaticPaymentMethodsParams{
-			AllowRedirects: stripe.String("never"),
-			Enabled:        stripe.Bool(true),
-		},
-	}
-
-	if paymentMethodID != "" {
-		params.PaymentMethod = stripe.String(paymentMethodID)
-		params.Confirm = stripe.Bool(true)
-	}
-
-	pi, err := paymentintent.New(params)
-	if err != nil {
-		return nil, fmt.Errorf("error creating payment intent: %w", err)
-	}
-
-	return &models.PaymentIntent{
-		ID:           pi.ID,
-		ClientSecret: pi.ClientSecret,
-		Amount:       pi.Amount,
-		Currency:     string(pi.Currency),
-		Status:       string(pi.Status),
-	}, nil
-}
-
-func (s *StripeClient) HandleWebhook(payload []byte, signature string) error {
-	event, err := webhook.ConstructEvent(payload, signature, s.webhookSecret)
-	if err != nil {
-		return fmt.Errorf("error verifying webhook signature: %w", err)
-	}
-
-	return s.processStripeEvent(event)
-}
-
-func (s *StripeClient) processStripeEvent(event stripe.Event) error {
-	switch event.Type {
-	case stripe.EventTypePaymentIntentSucceeded:
-		return s.handlePaymentIntentSucceeded(event)
-	case stripe.EventTypePaymentIntentPaymentFailed:
-		return s.handlePaymentIntentFailed(event)
-	// ... other event types
-	default:
-		log.Printf("Unhandled event type: %s", event.Type)
-		return nil
-	}
-}
-
-// Event handlers moved to separate methods
-func (s *StripeClient) handlePaymentIntentSucceeded(event stripe.Event) error {
-	var pi stripe.PaymentIntent
-	if err := json.Unmarshal(event.Data.Raw, &pi); err != nil {
-		return fmt.Errorf("error parsing PaymentIntent: %w", err)
-	}
-	log.Printf("PaymentIntent %s succeeded!", pi.ID)
-	return nil
-}
-
-func (s *StripeClient) handlePaymentIntentFailed(event stripe.Event) error {
-	var pi stripe.PaymentIntent
-	if err := json.Unmarshal(event.Data.Raw, &pi); err != nil {
-		return fmt.Errorf("error parsing PaymentIntent: %w", err)
-	}
-	log.Printf("PaymentIntent %s failed!", pi.ID)
-	return nil
-}

+ 182 - 220
internal/platform/mailer/mailer.go

@@ -19,67 +19,114 @@ const welcomeEmailTemplate = `
 <head>
 	<meta charset="UTF-8">
 	<meta name="viewport" content="width=device-width, initial-scale=1.0">
-	<title>Bienvenue sur Byom!</title>
+	<title>Bienvenue sur BYOM</title>
 	<style>
 		body {
-			font-family: Arial, sans-serif;
+			font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
 			line-height: 1.6;
-			color: #333;
+			color: #000;
+			background-color: #fff;
+			margin: 0;
+			padding: 40px 20px;
 		}
 		.container {
-			width: 80%;
+			max-width: 600px;
 			margin: 0 auto;
-			padding: 20px;
-			border: 1px solid #ddd;
-			border-radius: 5px;
-			background-color: #f9f9f9;
+			background-color: #fff;
+			border: 2px solid #000;
 		}
-		h1 {
-			color: #0056b3;
+		.header {
+			padding: 40px;
+			text-align: left;
+			border-bottom: 2px solid #000;
 		}
-		.info {
-			margin-bottom: 20px;
+		.header h1 {
+			margin: 0;
+			font-size: 32px;
+			font-weight: bold;
+			text-transform: uppercase;
+			letter-spacing: -1px;
 		}
-		.info p {
-			margin: 5px 0;
+		.content {
+			padding: 40px;
 		}
-		.subdomains {
-			margin-bottom: 20px;
+		.section {
+			margin-bottom: 40px;
+			border-bottom: 1px solid #000;
+			padding-bottom: 40px;
 		}
-		.subdomains p {
-			margin: 5px 0;
+		.info-grid {
+			display: grid;
+			grid-template-columns: auto 1fr;
+			gap: 10px;
+			margin: 20px 0;
+		}
+		.info-label {
+			font-weight: bold;
+			text-transform: uppercase;
+			font-size: 14px;
+		}
+		.info-value {
+			font-family: monospace;
+			background: #f0f0f0;
+			padding: 4px 8px;
+			border: 1px solid #000;
+		}
+		.urls {
+			margin: 20px 0;
+			padding: 20px;
+			border: 1px solid #000;
 		}
 		.footer {
-			margin-top: 20px;
-			font-size: 0.9em;
-			color: #777;
+			padding: 40px;
+			border-top: 2px solid #000;
+			font-size: 14px;
 		}
 	</style>
 </head>
 <body>
 	<div class="container">
-		<h1>Bienvenue sur Byom!</h1>
-		<p>Votre application a été configurée avec succès et est prête à être utilisée.</p>
-		<div class="info">
-			<h2>Informations d'accès :</h2>
-			<p><strong>Nom d'utilisateur :</strong> {{.Username}}</p>
-			<p><strong>Mot de passe :</strong> {{.Password}}</p>
+		<div class="header">
+			<h1>BYOM</h1>
 		</div>
-		<div class="subdomains">
-			<h2>URL de votre application :</h2>
-			{{range .Subdomains}}
-			<p>{{.}}</p>
+		<div class="content">
+			<div class="section">
+				<h2>Configuration terminée</h2>
+				<p>Votre application est prête à être utilisée.</p>
+			</div>
+			
+			<div class="section">
+				<h2>Informations d'accès</h2>
+				<div class="info-grid">
+					<div class="info-label">Utilisateur</div>
+					<div class="info-value">{{.Username}}</div>
+					
+					<div class="info-label">Mot de passe</div>
+					<div class="info-value">{{.Password}}</div>
+				</div>
+			</div>
+
+			<div class="section">
+				<h2>URLs d'accès</h2>
+				<div class="urls">
+					{{range .Subdomains}}
+					<p class="info-value">{{.}}</p>
+					{{end}}
+					<p><strong>Application principale:</strong></p>
+					<p class="info-value">{{.WebAppURL}}</p>
+				</div>
+			</div>
+
+			{{if .SetupGuide}}
+			<div class="section">
+				<h2>Guide de démarrage</h2>
+				<p>{{.SetupGuide}}</p>
+			</div>
 			{{end}}
-			<p><strong>Application principale :</strong> {{.WebAppURL}}</p>
-		</div>
-		<div class="setup-guide">
-			<h2>Pour bien commencer :</h2>
-			<p>{{.SetupGuide}}</p>
 		</div>
 		<div class="footer">
-			<p>Pour des raisons de sécurité, nous vous recommandons de changer votre mot de passe lors de votre première connexion.</p>
-			<p>Si vous avez besoin d'aide, n'hésitez pas à contacter notre équipe d'assistance.</p>
-			<p>Cordialement,<br>Byom</p>
+			<p>Pour des raisons de sécurité, changez votre mot de passe lors de votre première connexion.</p>
+			<p>Support technique: support@byom.fr</p>
 		</div>
 	</div>
 </body>
@@ -92,144 +139,87 @@ const verifyEmailTemplate = `
 <head>
 	<meta charset="UTF-8">
 	<meta name="viewport" content="width=device-width, initial-scale=1.0">
-	<title>Vérification de votre email - Byom</title>
+	<title>Vérification Email - BYOM</title>
 	<style>
 		body {
-			font-family: Arial, sans-serif;
+			font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
 			line-height: 1.6;
-			color: #333;
-			background-color: #f5f5f5;
+			color: #000;
+			background-color: #fff;
 			margin: 0;
-			padding: 20px;
+			padding: 40px 20px;
 		}
 		.container {
 			max-width: 600px;
 			margin: 0 auto;
-			background-color: #ffffff;
-			border-radius: 12px;
-			box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
-			overflow: hidden;
+			background-color: #fff;
+			border: 2px solid #000;
 		}
 		.header {
-			background-color: #0056b3;
-			color: white;
-			padding: 30px;
-			text-align: center;
+			padding: 40px;
+			text-align: left;
+			border-bottom: 2px solid #000;
 		}
 		.header h1 {
 			margin: 0;
-			font-size: 28px;
-			color: white;
+			font-size: 32px;
+			font-weight: bold;
+			text-transform: uppercase;
+			letter-spacing: -1px;
 		}
 		.content {
-			padding: 40px 30px;
+			padding: 40px;
+			border-bottom: 2px solid #000;
 		}
 		.message {
-			text-align: center;
-			margin-bottom: 30px;
-			font-size: 16px;
-			color: #555;
-		}
-		.button-container {
-			text-align: center;
-			margin: 30px 0;
+			font-size: 18px;
+			margin-bottom: 40px;
 		}
 		.button {
 			display: inline-block;
-			padding: 15px 35px;
-			background-color: #0056b3;
-			color: white;
+			padding: 16px 32px;
+			background-color: #000;
+			color: #fff;
 			text-decoration: none;
-			border-radius: 50px;
+			text-transform: uppercase;
 			font-weight: bold;
-			font-size: 16px;
-			transition: all 0.3s ease;
-			box-shadow: 0 2px 4px rgba(0, 86, 179, 0.3);
-		}
-		.button:hover {
-			background-color: #003d80;
-			transform: translateY(-2px);
-			box-shadow: 0 4px 8px rgba(0, 86, 179, 0.4);
+			letter-spacing: 1px;
+			margin: 20px 0;
 		}
 		.verification-link {
-			text-align: center;
 			margin: 20px 0;
-			padding: 15px;
-			background-color: #f8f9fa;
-			border-radius: 8px;
-			font-size: 14px;
-			color: #666;
+			padding: 20px;
+			background-color: #f0f0f0;
+			border: 1px solid #000;
 			word-break: break-all;
-		}
-		.divider {
-			height: 1px;
-			background-color: #eee;
-			margin: 30px 0;
+			font-family: monospace;
 		}
 		.footer {
-			text-align: center;
-			padding: 20px 30px;
-			background-color: #f8f9fa;
-			color: #666;
+			padding: 40px;
 			font-size: 14px;
-		}
-		.logo {
-			margin-bottom: 20px;
-		}
-		.security-notice {
-			background-color: #fff3cd;
-			border-left: 4px solid #ffc107;
-			padding: 15px;
-			margin: 20px 0;
-			font-size: 14px;
-			color: #856404;
-		}
-		.signature {
-			margin-top: 20px;
-			color: #0056b3;
-			font-weight: bold;
+			color: #666;
 		}
 	</style>
 </head>
 <body>
 	<div class="container">
 		<div class="header">
-			<div class="logo">
-				<!-- Vous pouvez ajouter votre logo ici -->
-				<h1>BYOM</h1>
-			</div>
-			<h2>Vérification de votre email</h2>
+			<h1>BYOM</h1>
 		</div>
-		
 		<div class="content">
 			<div class="message">
-				<h2>Merci de vous être inscrit!</h2>
-				<p>Pour finaliser votre inscription et accéder à toutes les fonctionnalités de Byom, veuillez vérifier votre adresse email.</p>
-			</div>
-
-			<div class="button-container">
-				<a href="{{.VerificationURL}}" class="button">Vérifier mon email</a>
-			</div>
-
-			<div class="security-notice">
-				<strong>🔒 Note de sécurité:</strong>
-				<p>Ce lien expirera dans 24 heures pour des raisons de sécurité.</p>
+				<h2>Vérification de votre email</h2>
+				<p>Pour finaliser votre inscription, veuillez vérifier votre adresse email.</p>
 			</div>
-
+			<a href="{{.VerificationURL}}" class="button">Vérifier mon email</a>
 			<div class="verification-link">
-				<p>Si le bouton ne fonctionne pas, copiez et collez ce lien dans votre navigateur:</p>
-				<p>{{.VerificationURL}}</p>
-			</div>
-
-			<div class="divider"></div>
-
-			<div class="footer">
-				<p>Si vous n'avez pas créé de compte sur Byom, vous pouvez ignorer cet email en toute sécurité.</p>
-				<div class="signature">
-					<p>Cordialement,<br>L'équipe Byom</p>
-				</div>
+				<p>Si le bouton ne fonctionne pas :</p>
+				<code>{{.VerificationURL}}</code>
 			</div>
 		</div>
+		<div class="footer">
+			<p>Si vous n'avez pas créé de compte sur BYOM, ignorez cet email.</p>
+		</div>
 	</div>
 </body>
 </html>
@@ -241,95 +231,66 @@ const workspaceWelcomeTemplate = `
 <head>
 	<meta charset="UTF-8">
 	<meta name="viewport" content="width=device-width, initial-scale=1.0">
-	<title>Votre espace de travail Byom est prêt !</title>
+	<title>Bienvenue sur BYOM</title>
 	<style>
 		body {
-			font-family: Arial, sans-serif;
+			font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
 			line-height: 1.6;
-			color: #333;
-			background-color: #f5f5f5;
+			color: #000;
+			background-color: #fff;
 			margin: 0;
-			padding: 20px;
+			padding: 40px 20px;
 		}
 		.container {
 			max-width: 600px;
 			margin: 0 auto;
-			background-color: #ffffff;
-			border-radius: 12px;
-			box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
-			overflow: hidden;
+			background-color: #fff;
+			border: 2px solid #000;
 		}
 		.header {
-			background-color: #0056b3;
-			color: white;
-			padding: 30px;
-			text-align: center;
+			padding: 40px;
+			text-align: left;
+			border-bottom: 2px solid #000;
 		}
 		.header h1 {
 			margin: 0;
-			font-size: 28px;
-			color: white;
+			font-size: 32px;
+			font-weight: bold;
+			text-transform: uppercase;
+			letter-spacing: -1px;
 		}
 		.content {
-			padding: 40px 30px;
+			padding: 40px;
 		}
-		.welcome-message {
-			text-align: center;
-			margin-bottom: 30px;
-			font-size: 18px;
-			color: #333;
-		}
-		.button-container {
-			text-align: center;
-			margin: 30px 0;
+		.section {
+			margin-bottom: 40px;
+			border-bottom: 1px solid #000;
+			padding-bottom: 40px;
 		}
 		.button {
 			display: inline-block;
-			padding: 15px 35px;
-			background-color: #0056b3;
-			color: white;
+			padding: 16px 32px;
+			background-color: #000;
+			color: #fff;
 			text-decoration: none;
-			border-radius: 50px;
+			text-transform: uppercase;
 			font-weight: bold;
-			font-size: 16px;
-			transition: all 0.3s ease;
-			box-shadow: 0 2px 4px rgba(0, 86, 179, 0.3);
-		}
-		.button:hover {
-			background-color: #003d80;
-			transform: translateY(-2px);
-			box-shadow: 0 4px 8px rgba(0, 86, 179, 0.4);
+			letter-spacing: 1px;
+			margin: 20px 0;
 		}
 		.features {
-			margin: 30px 0;
-			padding: 20px;
-			background-color: #f8f9fa;
-			border-radius: 8px;
-		}
-		.features h3 {
-			color: #0056b3;
-			margin-bottom: 15px;
+			display: grid;
+			grid-template-columns: repeat(2, 1fr);
+			gap: 20px;
+			margin: 40px 0;
 		}
-		.features ul {
-			list-style-type: none;
-			padding: 0;
-		}
-		.features li {
-			margin: 10px 0;
-			padding-left: 25px;
-			position: relative;
-		}
-		.features li:before {
-			content: "✓";
-			color: #0056b3;
-			position: absolute;
-			left: 0;
+		.feature {
+			padding: 20px;
+			border: 1px solid #000;
 		}
 		.footer {
-			text-align: center;
-			padding: 20px 30px;
-			background-color: #f8f9fa;
-			color: #666;
+			padding: 40px;
+			border-top: 2px solid #000;
 			font-size: 14px;
 		}
 	</style>
@@ -338,34 +299,35 @@ const workspaceWelcomeTemplate = `
 	<div class="container">
 		<div class="header">
 			<h1>BYOM</h1>
-			<h2>Votre espace de travail est prêt !</h2>
 		</div>
-		
 		<div class="content">
-			<div class="welcome-message">
-				<h2>Félicitations !</h2>
-				<p>Votre paiement a été confirmé et votre espace de travail Byom est maintenant prêt à être utilisé.</p>
-			</div>
-
-			<div class="button-container">
+			<div class="section">
+				<h2>Votre espace est prêt</h2>
+				<p>Votre paiement a été confirmé et votre espace BYOM est maintenant disponible.</p>
 				<a href="{{.WebAppURL}}" class="button">Accéder à mon espace</a>
 			</div>
-
 			<div class="features">
-				<h3>Ce qui vous attend :</h3>
-				<ul>
-					<li>Interface intuitive et personnalisable</li>
-					<li>Outils de collaboration avancés</li>
-					<li>Support technique dédié</li>
-					<li>Sécurité renforcée</li>
-				</ul>
-			</div>
-
-			<div class="footer">
-				<p>Si vous avez des questions, notre équipe de support est là pour vous aider.</p>
-				<p>Cordialement,<br>L'équipe Byom</p>
+				<div class="feature">
+					<h3>Interface</h3>
+					<p>Design minimaliste et efficace</p>
+				</div>
+				<div class="feature">
+					<h3>Collaboration</h3>
+					<p>Outils de travail en équipe</p>
+				</div>
+				<div class="feature">
+					<h3>Support</h3>
+					<p>Assistance technique dédiée</p>
+				</div>
+				<div class="feature">
+					<h3>Sécurité</h3>
+					<p>Protection des données</p>
+				</div>
 			</div>
 		</div>
+		<div class="footer">
+			<p>Support technique: support@byom.fr</p>
+		</div>
 	</div>
 </body>
 </html>
@@ -439,7 +401,7 @@ func (m *Mailer) SendVerifyEmail(to, token, plan string) error {
 	message.To(to)
 	message.Subject("Vérification de votre adresse email")
 
-	verificationURL := fmt.Sprintf("http://192.168.1.35:5173/verify-email?token=%s&plan=%s&email=%s",
+	verificationURL := fmt.Sprintf("https://byom.moooffle.com/verify-email?token=%s&plan=%s&email=%s",
 		token,
 		plan,
 		url.QueryEscape(to),

+ 172 - 15
internal/platform/payment/stripe.go

@@ -14,6 +14,8 @@ import (
 	"github.com/stripe/stripe-go/v81/account"
 	portalsession "github.com/stripe/stripe-go/v81/billingportal/session"
 	"github.com/stripe/stripe-go/v81/checkout/session"
+	"github.com/stripe/stripe-go/v81/price"
+	"github.com/stripe/stripe-go/v81/product"
 	"github.com/stripe/stripe-go/v81/webhook"
 )
 
@@ -23,7 +25,17 @@ type StripeHandler struct {
 	mailer         *mailer.Mailer
 }
 
-func NewStripeHandler(apiKey, endpointSecret, domain string, mailer *mailer.Mailer) (*StripeHandler, error) {
+func NewStripeHandler(domain string, mailer *mailer.Mailer) (*StripeHandler, error) {
+	apiKey := os.Getenv("STRIPE_SECRET_KEY")
+	if apiKey == "" {
+		return nil, fmt.Errorf("STRIPE_SECRET_KEY environment variable is not set")
+	}
+
+	endpointSecret := os.Getenv("STRIPE_WEBHOOK_SECRET")
+	if endpointSecret == "" {
+		return nil, fmt.Errorf("STRIPE_WEBHOOK_SECRET environment variable is not set")
+	}
+
 	stripe.Key = apiKey
 
 	// Test connection by retrieving account information
@@ -40,31 +52,31 @@ func NewStripeHandler(apiKey, endpointSecret, domain string, mailer *mailer.Mail
 }
 
 func (h *StripeHandler) CreateCheckoutSession(c *gin.Context) {
-	// Get email from request
+	// Get email and priceID from request
 	customerEmail := c.PostForm("email")
+	priceID := c.PostForm("priceId")
+
 	if customerEmail == "" {
 		c.JSON(http.StatusBadRequest, gin.H{"error": "Email is required"})
 		return
 	}
 
+	if priceID == "" {
+		c.JSON(http.StatusBadRequest, gin.H{"error": "Price ID is required"})
+		return
+	}
+
 	checkoutParams := &stripe.CheckoutSessionParams{
-		Mode: stripe.String(string(stripe.CheckoutSessionModePayment)),
+		Mode: stripe.String(string(stripe.CheckoutSessionModeSubscription)),
 		LineItems: []*stripe.CheckoutSessionLineItemParams{
 			{
-				PriceData: &stripe.CheckoutSessionLineItemPriceDataParams{
-					Currency: stripe.String("eur"),
-					ProductData: &stripe.CheckoutSessionLineItemPriceDataProductDataParams{
-						Name: stripe.String("Byom Subscription"),
-					},
-					UnitAmount: stripe.Int64(2000), // 20€ en centimes
-				},
+				Price:    stripe.String(priceID),
 				Quantity: stripe.Int64(1),
 			},
 		},
-		SuccessURL:       stripe.String("http://192.168.1.35:5173/payment/success?session_id={CHECKOUT_SESSION_ID}"),
-		CancelURL:        stripe.String("http://192.168.1.35:5173/payment/cancel"),
-		CustomerEmail:    stripe.String(customerEmail),
-		CustomerCreation: stripe.String(string(stripe.CheckoutSessionCustomerCreationAlways)),
+		SuccessURL:    stripe.String("https://byom.moooffle.com/payment/success?session_id={CHECKOUT_SESSION_ID}"),
+		CancelURL:     stripe.String("https://byom.moooffle.com/payment/cancel"),
+		CustomerEmail: stripe.String(customerEmail),
 	}
 
 	s, err := session.New(checkoutParams)
@@ -73,7 +85,6 @@ func (h *StripeHandler) CreateCheckoutSession(c *gin.Context) {
 		return
 	}
 
-	// Renvoyer uniquement l'URL et l'ID de session
 	c.JSON(http.StatusOK, gin.H{
 		"url":       s.URL,
 		"sessionId": s.ID,
@@ -191,3 +202,149 @@ func (h *StripeHandler) HandleWebhook(c *gin.Context) {
 
 	c.JSON(http.StatusOK, gin.H{"status": "success"})
 }
+
+//implement this func
+
+// const fetchPricingData = async () => {
+// 	try {
+// 		const response = await fetch('http://172.27.28.86:8080/api/v1/onboard/products');
+// 		if (!response.ok) throw new Error('Failed to fetch products');
+// 		const data = await response.json();
+
+// 		// Transform API data to match UI requirements
+// 		const transformedProducts = data.map(product => ({
+// 			name: product.name,
+// 			prices: {
+// 				monthly: product.prices.monthly,
+// 				yearly: product.prices.yearly,
+// 				monthlyPriceId: product.prices.monthly_price_id,
+// 				yearlyPriceId: product.prices.yearly_price_id
+// 			},
+// 			features: product.features,
+// 			description: product.description,
+// 			popular: product.metadata?.is_popular === "true",
+// 			maxUsers: product.max_users,
+// 			storageLimit: product.storage_limit,
+// 			apiRateLimit: product.api_rate_limit,
+// 			supportTier: product.support_tier,
+// 			customDomain: product.custom_domain
+// 		}));
+
+// 		setPricingData(transformedProducts);
+// 	} catch (error) {
+// 		console.error('Error fetching products:', error);
+// 		// Set fallback pricing data
+// 		setPricingData([
+// 			{
+// 				name: 'Starter',
+// 				prices: { monthly: 29, yearly: 290 },
+// 				features: [
+// 					'Basic AI trend analysis',
+// 					'25 AI image generations/month',
+// 					'Basic analytics dashboard',
+// 					'Content calendar',
+// 					'Email support',
+// 					'Single user'
+// 				],
+// 			},
+// 			// ... other fallback plans
+// 		]);
+// 	} finally {
+// 		setIsLoading(false);
+// 	}
+// };
+
+func (h *StripeHandler) GetProducts(c *gin.Context) {
+	params := &stripe.ProductListParams{
+		Active: stripe.Bool(true),
+	}
+	products := product.List(params)
+
+	var response []ProductResponse
+
+	for products.Next() {
+		p := products.Product()
+
+		// Parse product metadata
+		var metadata ProductMetadata
+		if metadataJSON, ok := p.Metadata["product_config"]; ok {
+			if err := json.Unmarshal([]byte(metadataJSON), &metadata); err != nil {
+				log.Printf("Error parsing metadata for product %s: %v", p.ID, err)
+				continue
+			}
+		}
+
+		// Fetch prices (rest of the price fetching logic remains the same)
+		priceParams := &stripe.PriceListParams{
+			Product: stripe.String(p.ID),
+			Active:  stripe.Bool(true),
+		}
+		prices := price.List(priceParams)
+
+		var monthlyPrice, yearlyPrice int64
+		var monthlyPriceID, yearlyPriceID string
+
+		for prices.Next() {
+			pr := prices.Price()
+			if pr.Recurring.Interval == "month" {
+				monthlyPrice = pr.UnitAmount / 100
+				monthlyPriceID = pr.ID
+			}
+			if pr.Recurring.Interval == "year" {
+				yearlyPrice = pr.UnitAmount / 100
+				yearlyPriceID = pr.ID
+			}
+		}
+
+		response = append(response, ProductResponse{
+			ID:          p.ID,
+			Name:        p.Name,
+			Description: metadata.Description,
+			Prices: Prices{
+				Monthly:        monthlyPrice,
+				Yearly:         yearlyPrice,
+				MonthlyPriceID: monthlyPriceID,
+				YearlyPriceID:  yearlyPriceID,
+			},
+			Features:     metadata.Features,
+			MaxUsers:     metadata.MaxUsers,
+			StorageLimit: metadata.StorageLimit,
+			ApiRateLimit: metadata.ApiRateLimit,
+			SupportTier:  metadata.SupportTier,
+			CustomDomain: metadata.CustomDomain,
+		})
+	}
+
+	c.JSON(200, response)
+}
+
+type ProductMetadata struct {
+	Description  string   `json:"description"`
+	Features     []string `json:"features"`
+	MaxUsers     int      `json:"max_users"`
+	StorageLimit string   `json:"storage_limit"`
+	ApiRateLimit int      `json:"api_rate_limit"`
+	SupportTier  string   `json:"support_tier"`
+	CustomDomain bool     `json:"custom_domain"`
+}
+
+type ProductResponse struct {
+	ID           string                 `json:"id"`
+	Name         string                 `json:"name"`
+	Description  string                 `json:"description"`
+	Prices       Prices                 `json:"prices"`
+	Features     []string               `json:"features"`
+	MaxUsers     int                    `json:"max_users"`
+	Metadata     map[string]interface{} `json:"metadata"`
+	StorageLimit string                 `json:"storage_limit"`
+	ApiRateLimit int                    `json:"api_rate_limit"`
+	SupportTier  string                 `json:"support_tier"`
+	CustomDomain bool                   `json:"custom_domain"`
+}
+
+type Prices struct {
+	Monthly        int64  `json:"monthly"`
+	Yearly         int64  `json:"yearly"`
+	MonthlyPriceID string `json:"monthly_price_id"`
+	YearlyPriceID  string `json:"yearly_price_id"`
+}

+ 30 - 0
internal/platform/stripe/stripe.go

@@ -0,0 +1,30 @@
+package stripe
+
+import (
+	"fmt"
+	"os"
+
+	"github.com/stripe/stripe-go/v81"
+	"github.com/stripe/stripe-go/v81/account"
+)
+
+// Initialize sets up the Stripe client with the provided API key
+func Initialize() error {
+	apiKey := os.Getenv("STRIPE_SECRET_KEY")
+	if apiKey == "" {
+		return fmt.Errorf("STRIPE_SECRET_KEY environment variable is not set")
+	}
+
+	// Set the API key for the Stripe client
+	stripe.Key = apiKey
+
+	// Test the connection
+	acc, err := account.Get()
+	if err != nil {
+		return fmt.Errorf("failed to connect to Stripe: %v", err)
+	}
+
+	fmt.Printf("Stripe account: %+v\n", acc)
+
+	return nil
+}