rentease/internal/repository/booking/pg_store.go
Ruidy 8384d85e3e
feat(stripe): add Stripe payment sync and webhook support
Introduce Stripe integration for automatic payment ingestion and refund
tracking. Adds new fields to the payment model for Stripe IDs and
status,
Stripe client driver, sync service, cron job, manual API endpoint, and
public webhook handler for real-time updates. Includes tests and
documentation. Manual cash entry remains supported.
2025-10-03 21:39:59 +02:00

221 lines
5.8 KiB
Go

package booking
import (
"errors"
"fmt"
"time"
"gorm.io/gorm"
"gorm.io/gorm/clause"
"github.com/rjNemo/rentease/internal/service/booking"
)
type PgStore struct {
db *gorm.DB
}
func NewPgStore(db *gorm.DB) *PgStore {
return &PgStore{db}
}
func (ps *PgStore) All() []*booking.Line {
bookings := make([]*booking.Line, 0)
ps.db.Raw(`
select bookings.id, customer_name, "from", "to", platform, sum(price * quantity) as total, canceled
from bookings
left join items on bookings.id = items.booking_id
group by bookings.id
order by id desc;
`).
Scan(&bookings)
return bookings
}
func (ps *PgStore) Search(value string) []*booking.Line {
bookings := make([]*booking.Line, 0)
ps.db.Raw(`
select bookings.id, customer_name, "from", "to", platform, sum(price * quantity) as total, canceled
from bookings
left join items on bookings.id = items.booking_id
where customer_name ilike ?
group by bookings.id
order by id desc;
`, fmt.Sprintf("%%%s%%", value)).
Scan(&bookings)
return bookings
}
func (ps *PgStore) List(from, to time.Time) ([]*booking.Line, error) {
bookings := make([]*booking.Line, 0)
if err := ps.db.Raw(`
select bookings.id, customer_name, "from", "to", platform, sum(price * quantity) as total, platform_fees
from bookings
join items on bookings.id = items.booking_id
where "to" between ? and ?
and canceled = false
group by bookings.id
order by id desc;
`, from, to).
Scan(&bookings).Error; err != nil {
return nil, fmt.Errorf("failed to list bookings: %w", err)
}
return bookings, nil
}
func (ps *PgStore) CardTotal(from, to time.Time) (float64, error) {
var total float64
if err := ps.db.Raw(`
select sum(total)
from (select sum(amount) as total
from bookings as b
join payments as p on b.id = p.booking_id
where "to" between ? and ?
and canceled = false
and p.payment_method = 'Card'
group by b.id) as t;
`, from, to).
Scan(&total).Error; err != nil {
return 0, fmt.Errorf("failed to get card total: %w", err)
}
return total, nil
}
func (ps *PgStore) Get(id int) *booking.Booking {
var b booking.Booking
ps.db.Preload("Items").Preload("Payments").First(&b, id)
return &b
}
func (ps *PgStore) Create(b *booking.Booking) error {
return ps.db.Create(b).Error
}
func (ps *PgStore) Update(b *booking.Booking) error {
return ps.db.Model(&booking.Booking{}).Where("id = ?", b.ID).Updates(map[string]any{
"from": b.From,
"to": b.To,
"customer_name": b.Name,
"phone_number": b.PhoneNumber,
"email": b.Email,
"platform": b.Platform,
"external_id": b.ExternalID,
"customers": b.CustomerNumber,
"platform_fees": b.PlatformFees,
}).Error
}
func (ps *PgStore) Cancel(id int) error {
return ps.db.Model(&booking.Booking{}).Where("id = ?", id).Update("canceled", true).Error
}
// Item methods
func (ps *PgStore) CreateItem(i *booking.Item) error {
return ps.db.Create(i).Error
}
func (ps *PgStore) PayItem(id int) (*booking.Item, error) {
i := new(booking.Item)
if err := ps.db.Model(i).
Clauses(clause.Returning{}).
Where("id = ?", id).
Update("payment_status", "Completed").
Error; err != nil {
return nil, err
}
return i, nil
}
func (ps *PgStore) GetItem(id int) (*booking.Item, error) {
i := &booking.Item{ID: id}
err := ps.db.First(i).Error
return i, err
}
func (ps *PgStore) UpdateItem(id int, item string, paymentMethod string, paymentStatus string, qty int, price float64) (*booking.Item, error) {
i := new(booking.Item)
err := ps.db.Model(i).
Clauses(clause.Returning{}).
Where("id = ?", id).
Updates(map[string]any{
"item": item,
"payment_method": paymentMethod,
"payment_status": paymentStatus,
"quantity": qty,
"price": price,
}).
Error
return i, err
}
func (ps *PgStore) CreatePayment(p *booking.Payment) (*booking.Payment, error) {
if err := ps.db.Create(p).Error; err != nil {
return nil, fmt.Errorf("failed to create payment: %w", err)
}
return p, nil
}
func (ps *PgStore) GetPayment(id int) (*booking.Payment, error) {
p := &booking.Payment{}
err := ps.db.First(p, id).Error
return p, err
}
func (ps *PgStore) UpdatePayment(id int, amount float64, paymentMethod string) (*booking.Payment, error) {
p := new(booking.Payment)
err := ps.db.Model(p).
Clauses(clause.Returning{}).
Where("id = ?", id).
Updates(map[string]any{
"amount": amount,
"payment_method": paymentMethod,
}).
Error
return p, err
}
func (ps *PgStore) UpsertStripePayment(p *booking.Payment) (*booking.Payment, error) {
if p.StripePaymentID == nil || *p.StripePaymentID == "" {
return nil, fmt.Errorf("stripe payment id is required")
}
existing := new(booking.Payment)
stripeID := *p.StripePaymentID
if err := ps.db.Where("stripe_payment_id = ?", stripeID).First(existing).Error; err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
if err := ps.db.Create(p).Error; err != nil {
return nil, fmt.Errorf("failed to create stripe payment: %w", err)
}
return p, nil
}
return nil, fmt.Errorf("failed to lookup stripe payment: %w", err)
}
updates := map[string]any{
"amount": p.Amount,
"payment_method": p.PaymentMethod,
"stripe_status": p.StripeStatus,
"booking_id": p.BookingID,
}
if err := ps.db.Model(existing).
Clauses(clause.Returning{}).
Updates(updates).
Error; err != nil {
return nil, fmt.Errorf("failed to update stripe payment: %w", err)
}
return existing, nil
}
func (ps *PgStore) FindStripePayment(stripePaymentID string) (*booking.Payment, error) {
if stripePaymentID == "" {
return nil, fmt.Errorf("stripe payment id is required")
}
p := new(booking.Payment)
if err := ps.db.Where("stripe_payment_id = ?", stripePaymentID).First(p).Error; err != nil {
return nil, err
}
return p, nil
}