backend-service/internal/services/payment_service.go
ats-tech25 a45b22afd0 feat(payments): Implement comprehensive Stripe payment integration
- Add PaymentHandler with methods for creating payment intents, confirming payments, and handling webhooks
- Implement PaymentService interface with Stripe payment processing logic
- Create DTOs for payment-related requests in services/interfaces.go
- Add error handling and validation for payment-related operations
- Configure Stripe API key and support for automatic payment methods
- Implement webhook signature verification and event processing
- Enhance error responses with detailed error messages and appropriate HTTP status codes
Enables full payment flow using Stripe, supporting intent creation, payment confirmation, and webhook handling for robust transaction management.
2025-11-05 15:30:53 +00:00

152 lines
4.6 KiB
Go

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
}