feat(payments): add gift card system v2 with management, expiry, inventory, audit log

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
2026-06-11 22:08:18 +01:00
co-authored by Sisyphus
parent 6756723c60
commit 30ded0bb95
7 changed files with 1479 additions and 69 deletions
+40 -9
View File
@@ -51,6 +51,7 @@ type PaymentRecord struct {
CreatedAt time.Time
UpdatedAt time.Time
CreatedBy *string
GiftCardID *string
}
type RefundRecord struct {
@@ -81,7 +82,7 @@ func (s *PaymentService) CalculateFees(amount int64, method string) float64 {
return float64(amount * 175 / 10000) / 100.0
}
func (s *PaymentService) CreatePaymentRecord(ctx context.Context, record PaymentRecord) (string, error) {
func (s *PaymentService) CreatePaymentRecord(ctx context.Context, record PaymentRecord, giftCardID *string) (string, error) {
var bookingID *string
if record.BookingID != "" {
bookingID = &record.BookingID
@@ -92,8 +93,9 @@ func (s *PaymentService) CreatePaymentRecord(ctx context.Context, record Payment
INSERT INTO payments (
booking_id, payment_type, payment_method, vendor_code, invoice_number,
status, amount, is_vat_applicable, vat_rate, vat_amount, net_amount,
user_saved_card_id, square_payment_id, idempotency_key, fees, created_at, updated_at, created_by
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18)
user_saved_card_id, square_payment_id, idempotency_key, fees, created_at, updated_at, created_by,
gift_card_id
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19)
RETURNING id
`,
bookingID,
@@ -114,6 +116,7 @@ func (s *PaymentService) CreatePaymentRecord(ctx context.Context, record Payment
record.CreatedAt,
record.UpdatedAt,
record.CreatedBy,
giftCardID,
).Scan(&id)
if err != nil {
@@ -170,7 +173,8 @@ func (s *PaymentService) GetBookingPaymentSummary(ctx context.Context, bookingID
rows, err := db.DB.Query(ctx, `
SELECT p.id, p.booking_id, p.payment_type, p.payment_method, p.vendor_code, p.invoice_number,
p.status, p.amount, COALESCE(usc.last_4, ''), p.is_vat_applicable, p.vat_rate, p.vat_amount, p.net_amount,
p.user_saved_card_id, p.square_payment_id, p.idempotency_key, p.fees, p.created_at, p.updated_at, p.created_by
p.user_saved_card_id, p.square_payment_id, p.idempotency_key, p.fees, p.created_at, p.updated_at, p.created_by,
p.gift_card_id
FROM payments p
LEFT JOIN user_saved_cards usc ON p.user_saved_card_id = usc.id
WHERE p.booking_id = $1
@@ -189,7 +193,7 @@ func (s *PaymentService) GetBookingPaymentSummary(ctx context.Context, bookingID
&p.ID, &p.BookingID, &p.PaymentType, &p.PaymentMethod, &p.VendorCode, &p.InvoiceNumber,
&p.Status, &p.Amount, &p.CardLast4, &p.IsVATApplicable, &p.VATRate, &p.VATAmount, &p.NetAmount,
&p.UserSavedCardID, &p.SquarePaymentID, &p.IdempotencyKey, &p.Fees,
&p.CreatedAt, &p.UpdatedAt, &p.CreatedBy,
&p.CreatedAt, &p.UpdatedAt, &p.CreatedBy, &p.GiftCardID,
)
if err != nil {
return nil, err
@@ -237,14 +241,40 @@ func (s *PaymentService) CheckIdempotency(ctx context.Context, bookingID, idempo
err := db.DB.QueryRow(ctx, `
SELECT id, booking_id, payment_type, payment_method, vendor_code, invoice_number,
status, amount, is_vat_applicable, vat_rate, vat_amount, net_amount,
user_saved_card_id, square_payment_id, idempotency_key, fees, created_at, updated_at, created_by
user_saved_card_id, square_payment_id, idempotency_key, fees, created_at, updated_at, created_by,
gift_card_id
FROM payments
WHERE booking_id = $1 AND idempotency_key = $2
`, bookingID, idempotencyKey).Scan(
&p.ID, &p.BookingID, &p.PaymentType, &p.PaymentMethod, &p.VendorCode, &p.InvoiceNumber,
&p.Status, &p.Amount, &p.IsVATApplicable, &p.VATRate, &p.VATAmount, &p.NetAmount,
&p.UserSavedCardID, &p.SquarePaymentID, &p.IdempotencyKey, &p.Fees,
&p.CreatedAt, &p.UpdatedAt, &p.CreatedBy,
&p.CreatedAt, &p.UpdatedAt, &p.CreatedBy, &p.GiftCardID,
)
if err != nil {
if errors.Is(err, pgx.ErrNoRows) {
return nil, nil
}
return nil, err
}
return &p, nil
}
func (s *PaymentService) CheckIdempotencyByKey(ctx context.Context, idempotencyKey string) (*PaymentRecord, error) {
var p PaymentRecord
err := db.DB.QueryRow(ctx, `
SELECT id, booking_id, payment_type, payment_method, vendor_code, invoice_number,
status, amount, is_vat_applicable, vat_rate, vat_amount, net_amount,
user_saved_card_id, square_payment_id, idempotency_key, fees, created_at, updated_at, created_by,
gift_card_id
FROM payments
WHERE idempotency_key = $1
`, idempotencyKey).Scan(
&p.ID, &p.BookingID, &p.PaymentType, &p.PaymentMethod, &p.VendorCode, &p.InvoiceNumber,
&p.Status, &p.Amount, &p.IsVATApplicable, &p.VATRate, &p.VATAmount, &p.NetAmount,
&p.UserSavedCardID, &p.SquarePaymentID, &p.IdempotencyKey, &p.Fees,
&p.CreatedAt, &p.UpdatedAt, &p.CreatedBy, &p.GiftCardID,
)
if err != nil {
@@ -261,14 +291,15 @@ func (s *PaymentService) GetPaymentByID(ctx context.Context, paymentID string) (
err := db.DB.QueryRow(ctx, `
SELECT id, booking_id, payment_type, payment_method, vendor_code, invoice_number,
status, amount, is_vat_applicable, vat_rate, vat_amount, net_amount,
user_saved_card_id, square_payment_id, idempotency_key, fees, created_at, updated_at, created_by
user_saved_card_id, square_payment_id, idempotency_key, fees, created_at, updated_at, created_by,
gift_card_id
FROM payments
WHERE id = $1
`, paymentID).Scan(
&p.ID, &p.BookingID, &p.PaymentType, &p.PaymentMethod, &p.VendorCode, &p.InvoiceNumber,
&p.Status, &p.Amount, &p.IsVATApplicable, &p.VATRate, &p.VATAmount, &p.NetAmount,
&p.UserSavedCardID, &p.SquarePaymentID, &p.IdempotencyKey, &p.Fees,
&p.CreatedAt, &p.UpdatedAt, &p.CreatedBy,
&p.CreatedAt, &p.UpdatedAt, &p.CreatedBy, &p.GiftCardID,
)
if err != nil {