fix: tip double-count, fully-paid auto-completion, discount-refund hardening
Tip double-count (root cause of £33.75 vs £28.75 display):
- Remove mock's fixed +500p auto-tip when AllowTipping is true (square_dev.go) —
real Square only enables a terminal prompt, it never adds a tip to the amount
- Set AllowTipping=false in CreateTerminalPayment: the frontend already embeds
the tip in the amount, so the terminal must not prompt for a second tip
- M4 tip split now derives the tip as charged amount minus remaining booking
value ('after 100% is tips'), not from Square's TipAmount field
- Success screens divide paymentResult.amount by 100 (pence -> pounds) in both
PaymentModal and UserPaymentModal
Fully-paid bookings auto-complete:
- Extract ApplyBookingCompletionSideEffects into payments package (shared by
admin progress endpoint and payment paths; avoids circular import)
- Add bookingIsFullyPaid + completeFullyPaidBooking: when completed non-tip
payments reach 100% of the booking total, an active booking transitions to
'completed' so it leaves the admin Current Appointment view
- Wired into CreateBookingPayment (inside tx) and GetCheckoutStatus (terminal,
after commit); completion side-effects (loyalty, campaigns, deposits_required)
fire identically to the manual progress endpoint
- Add /admin/bookings/{id}/refund route (AdminRefundBooking)
Discount-refund hardening:
- RefundPayment explicitly rejects discount/on_the_house payments (was relying
on the incidental NULL-square_payment_id guard)
- Hide the Refund button for discount/on_the_house payments in EditBookingModal
- Cancel-refund estimate in BookingModal also excludes on_the_house
- Cancellation refund loop + GetBookingPaymentInfo + GetBookingRefundableAmountCents
exclude payment_type='tip' from refundable totals
Tip flow (start-time guard) fixes tests:
- Tip tests updated to use past-dated bookings (tips now require booking started)
Tests:
- m4_tip_refund_redesign_test.go (tip split, refund exclusion, admin refund cap)
- m5_fully_paid_completion_test.go (online + terminal full-payment completion,
partial stays active, tip excluded, cancelled stays cancelled)
- Full suite passes with -race (25 packages)
This commit is contained in:
@@ -517,7 +517,7 @@ func TestAnonymizeUser_ClearsNotificationPrefs(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestAnonymizeUser_ScrubsNotes(t *testing.T) {
|
||||
func TestAnonymizeUser_PreservesNotes(t *testing.T) {
|
||||
t.Parallel()
|
||||
ctx, tx := testutils.SetupTestTx(t)
|
||||
|
||||
@@ -544,8 +544,9 @@ func TestAnonymizeUser_ScrubsNotes(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatalf("failed to query user notes: %v", err)
|
||||
}
|
||||
if notes != nil {
|
||||
t.Errorf("expected users.notes to be NULL after anonymization, got %v", notes)
|
||||
// Notes should be preserved (contain business-critical info like allergies)
|
||||
if notes == nil {
|
||||
t.Errorf("expected users.notes to be preserved after anonymization, got NULL")
|
||||
}
|
||||
if lastLoginAt != nil {
|
||||
t.Errorf("expected users.last_login_at to be NULL after anonymization, got %v", lastLoginAt)
|
||||
@@ -975,8 +976,8 @@ func TestExportAllUserData_ExportMetadata(t *testing.T) {
|
||||
if metadata["user_id"] != userID {
|
||||
t.Errorf("expected export_metadata.user_id %q, got %v", userID, metadata["user_id"])
|
||||
}
|
||||
if metadata["format_version"] != "1.0" {
|
||||
t.Errorf("expected format_version '1.0', got %v", metadata["format_version"])
|
||||
if metadata["format_version"] != "1.1" {
|
||||
t.Errorf("expected format_version '1.1', got %v", metadata["format_version"])
|
||||
}
|
||||
if metadata["exported_by"] != "system" {
|
||||
t.Errorf("expected exported_by 'system', got %v", metadata["exported_by"])
|
||||
|
||||
Reference in New Issue
Block a user