rentease/internal/service/booking/stripe_webhook.go

93 lines
2.5 KiB
Go

package booking
import (
"context"
"errors"
"log/slog"
"math"
"strings"
"github.com/stripe/stripe-go/v83"
"gorm.io/gorm"
stripeclient "github.com/rjNemo/rentease/internal/driver/stripe"
)
// HandlePaymentIntentSucceeded persists successful Stripe payment intents received via webhook.
func (bs Service) HandlePaymentIntentSucceeded(ctx context.Context, pi *stripe.PaymentIntent) error {
if pi == nil {
return errors.New("payment intent payload is missing")
}
normalized := stripeclient.NormalizePaymentIntent(pi)
if normalized.ID == "" {
return errors.New("payment intent missing id")
}
if normalized.BookingID == nil {
bs.logger.Warn("stripe webhook payment missing booking metadata", slog.String("payment_intent", normalized.ID))
return nil
}
bookingID := uint(*normalized.BookingID)
stripeID := normalized.ID
status := strings.ToLower(normalized.Status)
_, err := bs.store.UpsertStripePayment(&Payment{
BookingID: bookingID,
Amount: normalized.Amount,
PaymentMethod: mapStripeMethod(normalized.PaymentMethod),
StripePaymentID: &stripeID,
StripeStatus: &status,
})
if err != nil {
return err
}
bs.logger.Info("stripe payment intent processed", slog.String("payment_intent", normalized.ID), slog.Int("booking_id", int(bookingID)))
return nil
}
// HandleChargeRefunded updates an existing Stripe payment when a charge is refunded.
func (bs Service) HandleChargeRefunded(ctx context.Context, ch *stripe.Charge) error {
if ch == nil {
return errors.New("charge payload is missing")
}
if ch.PaymentIntent == nil || ch.PaymentIntent.ID == "" {
bs.logger.Warn("stripe refund missing payment intent", slog.String("charge", ch.ID))
return nil
}
existing, err := bs.store.FindStripePayment(ch.PaymentIntent.ID)
if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
bs.logger.Warn("stripe refund received for unknown payment", slog.String("payment_intent", ch.PaymentIntent.ID))
return nil
}
return err
}
amount := existing.Amount
if ch.AmountRefunded > 0 {
net := float64(ch.Amount-ch.AmountRefunded) / 100.0
amount = math.Max(net, 0)
}
status := "refunded"
stripeID := ch.PaymentIntent.ID
_, err = bs.store.UpsertStripePayment(&Payment{
BookingID: existing.BookingID,
Amount: amount,
PaymentMethod: existing.PaymentMethod,
StripePaymentID: &stripeID,
StripeStatus: &status,
})
if err != nil {
return err
}
bs.logger.Info("stripe charge refunded processed", slog.String("charge", ch.ID), slog.String("payment_intent", ch.PaymentIntent.ID))
return nil
}