mirror of
https://github.com/rjNemo/rentease.git
synced 2026-06-10 20:56:50 +00:00
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.
221 lines
5.8 KiB
Go
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
|
|
}
|