Add GetVATConfig for reading VAT settings via any db.Querier, ApplyVATToBookingPayment for applying VAT to payment records, and ApplyVATToTillSale for SPV gift card till sales. Errors are logged without blocking payment flow. 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 and on_the_house payments must never have VAT applied.
|
|
// Read the payment method 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 string
|
|
if qErr := q.QueryRow(ctx, "SELECT payment_method FROM payments WHERE id = $1", paymentID).Scan(&method); qErr == nil && (method == "discount" || method == "on_the_house") {
|
|
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)
|
|
}
|
|
}
|
|
|
|
|