backend-service/internal/services/payment_service.go

152 lines
4.6 KiB
Go
Raw Normal View History

package services
import (
"encoding/json"
"fmt"
"log"
"attune-heart-therapy/internal/config"
"github.com/stripe/stripe-go/v76"
"github.com/stripe/stripe-go/v76/paymentintent"
"github.com/stripe/stripe-go/v76/webhook"
)
// paymentService implements the PaymentService interface
type paymentService struct {
config *config.Config
}
// NewPaymentService creates a new instance of PaymentService
func NewPaymentService(cfg *config.Config) PaymentService {
// Set Stripe API key
stripe.Key = cfg.Stripe.SecretKey
return &paymentService{
config: cfg,
}
}
// CreatePaymentIntent creates a new Stripe payment intent for payment initialization
func (s *paymentService) CreatePaymentIntent(amount float64, currency string) (*stripe.PaymentIntent, error) {
if amount <= 0 {
return nil, fmt.Errorf("amount must be greater than 0")
}
if currency == "" {
currency = "usd" // Default to USD
}
// Convert amount to cents (Stripe expects amounts in smallest currency unit)
amountCents := int64(amount * 100)
params := &stripe.PaymentIntentParams{
Amount: stripe.Int64(amountCents),
Currency: stripe.String(currency),
AutomaticPaymentMethods: &stripe.PaymentIntentAutomaticPaymentMethodsParams{
Enabled: stripe.Bool(true),
},
}
pi, err := paymentintent.New(params)
if err != nil {
log.Printf("Failed to create payment intent: %v", err)
return nil, fmt.Errorf("failed to create payment intent: %w", err)
}
return pi, nil
}
// ConfirmPayment confirms a payment intent for payment completion
func (s *paymentService) ConfirmPayment(paymentIntentID string) (*stripe.PaymentIntent, error) {
if paymentIntentID == "" {
return nil, fmt.Errorf("payment intent ID is required")
}
params := &stripe.PaymentIntentConfirmParams{}
pi, err := paymentintent.Confirm(paymentIntentID, params)
if err != nil {
log.Printf("Failed to confirm payment intent %s: %v", paymentIntentID, err)
return nil, fmt.Errorf("failed to confirm payment: %w", err)
}
return pi, nil
}
// HandleWebhook processes Stripe webhook events for webhook processing
func (s *paymentService) HandleWebhook(payload []byte, signature string) error {
if len(payload) == 0 {
return fmt.Errorf("webhook payload is empty")
}
if signature == "" {
return fmt.Errorf("webhook signature is required")
}
// Verify webhook signature
event, err := webhook.ConstructEvent(payload, signature, s.config.Stripe.WebhookSecret)
if err != nil {
log.Printf("Failed to verify webhook signature: %v", err)
return fmt.Errorf("failed to verify webhook signature: %w", err)
}
// Handle different event types
switch event.Type {
case "payment_intent.succeeded":
var paymentIntent stripe.PaymentIntent
objectBytes, err := json.Marshal(event.Data.Object)
if err != nil {
log.Printf("Failed to marshal event data: %v", err)
return fmt.Errorf("failed to parse event data: %w", err)
}
if err := json.Unmarshal(objectBytes, &paymentIntent); err != nil {
log.Printf("Failed to unmarshal payment intent: %v", err)
return fmt.Errorf("failed to parse payment intent: %w", err)
}
log.Printf("Payment succeeded for payment intent: %s", paymentIntent.ID)
// TODO: Update booking status to confirmed
// This will be handled when booking service is integrated
case "payment_intent.payment_failed":
var paymentIntent stripe.PaymentIntent
objectBytes, err := json.Marshal(event.Data.Object)
if err != nil {
log.Printf("Failed to marshal event data: %v", err)
return fmt.Errorf("failed to parse event data: %w", err)
}
if err := json.Unmarshal(objectBytes, &paymentIntent); err != nil {
log.Printf("Failed to unmarshal payment intent: %v", err)
return fmt.Errorf("failed to parse payment intent: %w", err)
}
log.Printf("Payment failed for payment intent: %s", paymentIntent.ID)
// TODO: Update booking status to failed
// This will be handled when booking service is integrated
case "payment_intent.canceled":
var paymentIntent stripe.PaymentIntent
objectBytes, err := json.Marshal(event.Data.Object)
if err != nil {
log.Printf("Failed to marshal event data: %v", err)
return fmt.Errorf("failed to parse event data: %w", err)
}
if err := json.Unmarshal(objectBytes, &paymentIntent); err != nil {
log.Printf("Failed to unmarshal payment intent: %v", err)
return fmt.Errorf("failed to parse payment intent: %w", err)
}
log.Printf("Payment canceled for payment intent: %s", paymentIntent.ID)
// TODO: Update booking status to canceled
// This will be handled when booking service is integrated
default:
log.Printf("Unhandled webhook event type: %s", event.Type)
}
return nil
}