152 lines
4.6 KiB
Go
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
|
||
|
|
}
|