Refactor BuyGiftCard to insert pending payment before Square call with VAT applied. Add transaction wrapping to gift card handlers. Remove redundant Content-Type header sets. Migrate all time.Now() to clock.Now(). Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
79 lines
2.7 KiB
Go
79 lines
2.7 KiB
Go
package payments
|
|
|
|
import (
|
|
"context"
|
|
"log"
|
|
|
|
"crussell/db"
|
|
)
|
|
|
|
type VATConfig struct {
|
|
IsVATRegistered bool
|
|
DefaultVATRate float64
|
|
VoucherType string
|
|
}
|
|
|
|
// GetVATConfig reads VAT config using the given querier, which can be either
|
|
// a *db.PoolProxy (for non-transactional reads), a pgx.Tx (to read inside a
|
|
// transaction), or any other type that implements db.Querier.
|
|
func GetVATConfig(ctx context.Context, q db.Querier) (*VATConfig, error) {
|
|
var cfg VATConfig
|
|
err := q.QueryRow(ctx, `
|
|
SELECT COALESCE(is_vat_registered, FALSE),
|
|
COALESCE(default_vat_rate, 20.0),
|
|
COALESCE(voucher_type, 'SPV')
|
|
FROM business_settings
|
|
LIMIT 1
|
|
`).Scan(&cfg.IsVATRegistered, &cfg.DefaultVATRate, &cfg.VoucherType)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &cfg, nil
|
|
}
|
|
|
|
// ApplyVATToBookingPayment reads VAT config and calls apply_vat_to_payment
|
|
// on a booking payment record. The q parameter is used for both reading the
|
|
// VAT config and for the defensive payment-method check, ensuring all reads
|
|
// are consistent with the caller's transactional context. Errors are logged
|
|
// — VAT failure should not block the payment flow.
|
|
func ApplyVATToBookingPayment(ctx context.Context, q db.Querier, paymentID string) {
|
|
vatCfg, err := GetVATConfig(ctx, q)
|
|
if err != nil {
|
|
log.Printf("Failed to read VAT config for payment %s: %v", paymentID, err)
|
|
return
|
|
}
|
|
if !vatCfg.IsVATRegistered {
|
|
return
|
|
}
|
|
// Discount, on_the_house, and tip payments must never have VAT applied.
|
|
// Read the payment method and type inside the same transactional context
|
|
// so that the just-inserted row is visible (defence against READ
|
|
// COMMITTED isolation when q is a pgx.Tx).
|
|
var method, ptype string
|
|
if qErr := q.QueryRow(ctx, "SELECT payment_method, payment_type FROM payments WHERE id = $1", paymentID).Scan(&method, &ptype); qErr == nil && (method == "discount" || method == "on_the_house" || ptype == "tip") {
|
|
return
|
|
}
|
|
if _, execErr := q.Exec(ctx, "SELECT apply_vat_to_payment($1, $2)", paymentID, vatCfg.DefaultVATRate); execErr != nil {
|
|
log.Printf("Failed to apply VAT to payment %s: %v", paymentID, execErr)
|
|
}
|
|
}
|
|
|
|
// ApplyVATToTillSale reads VAT config and calls apply_vat_to_till_sale for
|
|
// SPV gift card till sales. Errors are logged — VAT failure should not
|
|
// block the sale flow.
|
|
func ApplyVATToTillSale(ctx context.Context, q db.Querier, saleID string) {
|
|
vatCfg, err := GetVATConfig(ctx, q)
|
|
if err != nil {
|
|
log.Printf("Failed to read VAT config for till sale %s: %v", saleID, err)
|
|
return
|
|
}
|
|
if !vatCfg.IsVATRegistered || vatCfg.VoucherType != "SPV" {
|
|
return
|
|
}
|
|
if _, execErr := q.Exec(ctx, "SELECT apply_vat_to_till_sale($1, $2)", saleID, vatCfg.DefaultVATRate); execErr != nil {
|
|
log.Printf("Failed to apply VAT to till sale %s: %v", saleID, execErr)
|
|
}
|
|
}
|
|
|
|
|