123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350 |
- package payment
- import (
- "encoding/json"
- "fmt"
- "io"
- "log"
- "net/http"
- "os"
- "git.linuxforward.com/byom/byom-onboard/internal/platform/mailer"
- "github.com/gin-gonic/gin"
- "github.com/stripe/stripe-go/v81"
- "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"
- )
- type StripeHandler struct {
- domain string
- endpointSecret string
- mailer *mailer.Mailer
- }
- 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
- _, err := account.Get()
- if err != nil {
- return nil, fmt.Errorf("failed to connect to Stripe: %w", err)
- }
- return &StripeHandler{
- domain: domain,
- endpointSecret: endpointSecret,
- mailer: mailer,
- }, nil
- }
- func (h *StripeHandler) CreateCheckoutSession(c *gin.Context) {
- // 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.CheckoutSessionModeSubscription)),
- LineItems: []*stripe.CheckoutSessionLineItemParams{
- {
- Price: stripe.String(priceID),
- Quantity: stripe.Int64(1),
- },
- },
- 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)
- if err != nil {
- c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
- return
- }
- c.JSON(http.StatusOK, gin.H{
- "url": s.URL,
- "sessionId": s.ID,
- })
- }
- func (h *StripeHandler) CreatePortalSession(c *gin.Context) {
- sessionID := c.PostForm("session_id")
- s, err := session.Get(sessionID, nil)
- if err != nil {
- c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
- log.Printf("session.Get: %v", err)
- return
- }
- params := &stripe.BillingPortalSessionParams{
- Customer: stripe.String(s.Customer.ID),
- ReturnURL: stripe.String(h.domain),
- }
- ps, err := portalsession.New(params)
- if err != nil {
- c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
- log.Printf("ps.New: %v", err)
- return
- }
- c.Redirect(http.StatusSeeOther, ps.URL)
- }
- func (h *StripeHandler) HandleWebhook(c *gin.Context) {
- const MaxBodyBytes = int64(65536)
- bodyReader := http.MaxBytesReader(c.Writer, c.Request.Body, MaxBodyBytes)
- payload, err := io.ReadAll(bodyReader)
- if err != nil {
- log.Printf("[Webhook Debug] Error reading body: %v", err)
- c.JSON(http.StatusServiceUnavailable, gin.H{"error": "Service unavailable"})
- return
- }
- log.Printf("[Webhook Debug] Received payload: %s", string(payload))
- signatureHeader := c.Request.Header.Get("Stripe-Signature")
- log.Printf("[Webhook Debug] Stripe-Signature header: %s", signatureHeader)
- log.Printf("[Webhook Debug] Using endpoint secret: %s", h.endpointSecret)
- event, err := webhook.ConstructEvent(payload, signatureHeader, h.endpointSecret)
- if err != nil {
- log.Printf("[Webhook Debug] Signature verification failed: %v", err)
- log.Printf("[Webhook Debug] Headers received: %+v", c.Request.Header)
- c.JSON(http.StatusBadRequest, gin.H{"error": "Bad request"})
- return
- }
- log.Printf("[Webhook Debug] Event type received: %s", event.Type)
- log.Printf("[Webhook Debug] Event data: %s", string(event.Data.Raw))
- switch event.Type {
- case "customer.subscription.deleted":
- var subscription stripe.Subscription
- if err := json.Unmarshal(event.Data.Raw, &subscription); err != nil {
- c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
- return
- }
- log.Printf("Subscription deleted for %s.", subscription.ID)
- case "customer.subscription.updated":
- var subscription stripe.Subscription
- if err := json.Unmarshal(event.Data.Raw, &subscription); err != nil {
- c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
- return
- }
- log.Printf("Subscription updated for %s.", subscription.ID)
- case "customer.subscription.created":
- var subscription stripe.Subscription
- if err := json.Unmarshal(event.Data.Raw, &subscription); err != nil {
- c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
- return
- }
- log.Printf("Subscription created for %s.", subscription.ID)
- case "checkout.session.completed":
- var session stripe.CheckoutSession
- if err := json.Unmarshal(event.Data.Raw, &session); err != nil {
- log.Printf("[Webhook Debug] Failed to parse session data: %v", err)
- c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
- return
- }
- log.Printf("[Webhook Debug] Payment successful for session: %s", session.ID)
- customerEmail := session.CustomerEmail
- log.Printf("[Webhook Debug] Customer email: %s", customerEmail)
- if err := h.mailer.SendWelcomeEmail(customerEmail, &mailer.EmailData{
- Username: customerEmail,
- WebAppURL: fmt.Sprintf("http://test.byom.fr/login?email=%s", customerEmail),
- SetupGuide: "Votre espace de travail est prêt ! Connectez-vous pour commencer à utiliser Byom.",
- }); err != nil {
- log.Printf("[Webhook Debug] Failed to send welcome email: %v", err)
- }
- case "checkout.session.expired":
- var session stripe.CheckoutSession
- if err := json.Unmarshal(event.Data.Raw, &session); err != nil {
- c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
- return
- }
- log.Printf("Payment session expired: %s", session.ID)
- // TODO: Gérer l'expiration de la session
- default:
- fmt.Fprintf(os.Stderr, "Unhandled event type: %s\n", event.Type)
- }
- 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"`
- }
|