Allow refunding only the card processing (convenience) fee

Objective

  • Let staff return only the pass-through card processing / convenience fee to the payer’s card without having to refund a slice of the inspection payment that forces a proportional fee refund today.
  • Remove the gap where finance or support must use processor or manual work outside Attik when the goal is simply to reverse or waive the fee after capture (goodwill, mistaken pass-through, policy).

Background

  • Guardian (CardPointe) capture, when pass-through is enabled and fees are not disabled on the job, creates a linked Fee record and stores fee metadata on Payment on the guardianPayments capture path.
  • POST /guardian-payments/refund in attik-backend/src/routes/guardianPayments.ts requires a positive refundAmount, caps settled refunds against remaining refundable payment, and computes proportionalFee = (refundAmount / payment.amount) * feeAmount so the amount sent to the processor is refundAmount + proportionalFee. A fee-only refund would imply refundAmount === 0, which the route rejects today.
  • Void reverses the full capture (payment + fee), not the fee alone.
  • Queued refunds and follow-up processing live in attik-backend/src/util/functions/guardian/checkRefundStatus.ts (batched Guardian refunds); parity with synchronous refund ledger behavior needs to stay consistent when semantics change.
  • Spectora-synced payments remain blocked from in-app Guardian refund in the same route—treat that as unchanged unless explicitly expanded later.

Scope

Backend

  • Refund semantics for Guardian: attik-backend/src/routes/guardianPayments.ts (router.post('/refund', …)), including validation (refundAmount > 0), ACH partial-refund rules, inquiry-driven void vs refund vs queued paths, and refundOrVoidTransaction / refundTransaction usage from attik-backend/src/util/functions/guardian/cardpointeApi.ts.
  • Ledger consistency: Refund creation (including feeRefundAmount), Fee updates (including negative fee / refunded flags on settled refunds), and how partial refunds compose with any new fee-only path.
  • checkRefundStatus.ts: ensure queued / batched refund processing still matches synchronous expectations when fee-only or split semantics exist.
  • Finix parity is a product decision: fee representation touches attik-backend/src/routes/webhooks/finix/transfers.ts (e.g. feeless_total on transfer tags) and may need aligned behavior if Guardian gains fee-only refunds.

Frontend

  • Staff refund UX and server actions: attik-frontend/src/app/tools/inspections/[id]/components/RefundModal.tsx (Guardian vs Finix branching, refundGuardianPayment from refundPayment actions, getPaymentFeeAmount, max amount / schema validation) so users can choose fee-only vs payment refund safely and copy prevents double-refunding fees.

Out of scope unless explicitly expanded

  • Spectora-synced payments in-app refund (still rejected by backend today).

Acceptance criteria (draft)

  • [ ] Staff can trigger a refund that returns only the fee (or an agreed subset) to the payer’s card while payment / invoice application matches the chosen product rule (e.g. payment unchanged vs a recorded adjustment—decision needed where product has not locked one rule).
  • [ ] Ledger: Fee, Refund, and batch views stay internally consistent; no duplicate fee refunds.
  • [ ] Guardrails: permissions, idempotency, clear errors when no fee, fee already refunded, or processor rejects the operation.
  • [ ] Document CardPointe constraints (minimum amounts, ACH partial rules) wherever staff-facing errors or internal runbooks are updated.

References

  • Guardian refund + fee math: attik-backend/src/routes/guardianPayments.ts (refund handler: proportionalFee, amountToSendToProcessor, Refund / feeRefundAmount, fee marking on void vs refund).
  • CardPointe helpers: attik-backend/src/util/functions/guardian/cardpointeApi.ts (refundOrVoidTransaction, refundTransaction).
  • Queued refunds: attik-backend/src/util/functions/guardian/checkRefundStatus.ts.
  • Refund UI: attik-frontend/src/app/tools/inspections/[id]/components/RefundModal.tsx.
  • Finix fee tagging (parity research): attik-backend/src/routes/webhooks/finix/transfers.ts (feeless_total).

Please authenticate to join the conversation.

Upvoters
Status

Completed

Board
🏠

Main App

Date

18 days ago

Author

Linear

Subscribe to post

Get notified by email when there are changes.