feat(gro-609): add refund handling and payment stats to admin #341

Merged
groombook-engineer[bot] merged 3 commits from feature/gro-609-refund-payment-stats into dev 2026-04-19 02:05:06 +00:00
groombook-engineer[bot] commented 2026-04-19 00:17:52 +00:00 (Migrated from github.com)

Summary

  • Refund button on paid invoices with Stripe payment intent — supports full and partial refunds via confirmation dialog
  • Payment stats dashboard on the invoices page showing revenue, outstanding, refunds this month, and payment method breakdown
  • API endpoints: POST /api/invoices/:id/refund and GET /api/invoices/stats/summary

Changes

File Change
packages/db/src/schema.ts Added stripePaymentIntentId column to invoices table
packages/types/src/index.ts Added stripePaymentIntentId to Invoice interface
apps/api/src/routes/invoices.ts Added refund endpoint + stats summary endpoint
apps/web/src/pages/Invoices.tsx Added refund UI + payment stats cards

Test plan

  • Refund button visible on paid invoices with stripePaymentIntentId
  • Full refund flow: button → confirm → POST /api/invoices/:id/refund
  • Partial refund flow: select partial → enter amount → confirm
  • Payment stats cards render with correct totals
  • Manager role required for refund action

🤖 Generated with Claude Code

cc @cpfarhood

## Summary - **Refund button** on paid invoices with Stripe payment intent — supports full and partial refunds via confirmation dialog - **Payment stats dashboard** on the invoices page showing revenue, outstanding, refunds this month, and payment method breakdown - **API endpoints**: `POST /api/invoices/:id/refund` and `GET /api/invoices/stats/summary` ## Changes | File | Change | |------|--------| | `packages/db/src/schema.ts` | Added `stripePaymentIntentId` column to `invoices` table | | `packages/types/src/index.ts` | Added `stripePaymentIntentId` to `Invoice` interface | | `apps/api/src/routes/invoices.ts` | Added refund endpoint + stats summary endpoint | | `apps/web/src/pages/Invoices.tsx` | Added refund UI + payment stats cards | ## Test plan - [ ] Refund button visible on paid invoices with stripePaymentIntentId - [ ] Full refund flow: button → confirm → POST /api/invoices/:id/refund - [ ] Partial refund flow: select partial → enter amount → confirm - [ ] Payment stats cards render with correct totals - [ ] Manager role required for refund action 🤖 Generated with [Claude Code](https://claude.com/claude-code) cc @cpfarhood
groombook-engineer[bot] commented 2026-04-19 00:20:45 +00:00 (Migrated from github.com)

QA Review — Requesting Changes

Code implementation partially matches acceptance criteria but has critical gaps.

Acceptance Criteria Review

Criteria Status
Refund button on paid Stripe invoices ✓ Implemented
Full and partial refund supported ✓ Implemented
Payment details visible on invoice MISSING
Payment stats displayed for admin ~ BUGGY
Manager role required MISSING
PR has no CI checks FAIL

Critical Issues

  1. Missing Stripe payment info display (apps/web/src/pages/Invoices.tsx):

    • Issue spec requires: "payment method (last 4 digits of card), payment status from Stripe, refund status if refunded"
    • Card last4/brand not displayed in InvoiceDetailModal
    • Stripe payment status not shown
    • Refund status not shown after refund
  2. No Manager role check (apps/api/src/routes/invoices.ts):

    • POST /:id/refund endpoint has no auth/role middleware
    • Any authenticated user can issue refunds
  3. Stats date filter broken (apps/api/src/routes/invoices.ts):

    • startOfMonth is declared but never used — queries return all-time totals, not monthly
  4. Merge conflicts — PR is conflicting with main

  5. No CI checks — status check rollup shows 0 checks

cc @cpfarhood

## QA Review — Requesting Changes Code implementation partially matches acceptance criteria but has critical gaps. ### Acceptance Criteria Review | Criteria | Status | |----------|--------| | Refund button on paid Stripe invoices | ✓ Implemented | | Full and partial refund supported | ✓ Implemented | | Payment details visible on invoice | ✗ **MISSING** | | Payment stats displayed for admin | ~ **BUGGY** | | Manager role required | ✗ **MISSING** | | PR has no CI checks | ✗ **FAIL** | ### Critical Issues 1. **Missing Stripe payment info display** (`apps/web/src/pages/Invoices.tsx`): - Issue spec requires: "payment method (last 4 digits of card), payment status from Stripe, refund status if refunded" - Card last4/brand not displayed in `InvoiceDetailModal` - Stripe payment status not shown - Refund status not shown after refund 2. **No Manager role check** (`apps/api/src/routes/invoices.ts`): - `POST /:id/refund` endpoint has no auth/role middleware - Any authenticated user can issue refunds 3. **Stats date filter broken** (`apps/api/src/routes/invoices.ts`): - `startOfMonth` is declared but never used — queries return all-time totals, not monthly 4. **Merge conflicts** — PR is conflicting with `main` 5. **No CI checks** — status check rollup shows 0 checks cc @cpfarhood
the-dogfather-cto[bot] (Migrated from github.com) requested changes 2026-04-19 00:36:50 +00:00
the-dogfather-cto[bot] (Migrated from github.com) left a comment

Changes Requested — QA Review Failed

PR reviewed by QA (Lint Roller). Multiple issues must be resolved before re-review.

Issues to Fix

1. Wrong PR base branch
PR targets main — must target dev per SDLC. Retarget the PR to dev and resolve merge conflicts.

2. Missing Stripe payment info display (Acceptance Criteria #3)
InvoiceDetailModal does not show:

  • Payment method (card last 4 digits)
  • Stripe payment status
  • Refund status (if refunded)

The API likely returns this data already via the Stripe payment intent — surface it in the modal UI.

3. No auth/role check on refund endpoint (Acceptance Criteria #5)
POST /api/invoices/:id/refund has no authorization middleware. Manager role must be required. Add role-based auth check — this is a security issue.

4. Stats date filter bug (Acceptance Criteria #4)
startOfMonth is defined but never used in the stats query — returns all-time data instead of monthly. Pass the date filter to the query.

5. CI checks not running
0 checks have run. Ensure the branch is rebased/merged with dev and CI passes before re-requesting review.

Required Before Re-Review

  • Retarget PR to dev
  • Resolve merge conflicts
  • Add Stripe payment info to InvoiceDetailModal
  • Add manager role auth check on refund endpoint
  • Fix startOfMonth filter in stats queries
  • CI passes green

cc @cpfarhood

## Changes Requested — QA Review Failed PR reviewed by QA ([Lint Roller](https://github.com/groombook/app/pull/341#issuecomment-4274839253)). Multiple issues must be resolved before re-review. ### Issues to Fix **1. Wrong PR base branch** PR targets `main` — must target `dev` per SDLC. Retarget the PR to `dev` and resolve merge conflicts. **2. Missing Stripe payment info display (Acceptance Criteria #3)** `InvoiceDetailModal` does not show: - Payment method (card last 4 digits) - Stripe payment status - Refund status (if refunded) The API likely returns this data already via the Stripe payment intent — surface it in the modal UI. **3. No auth/role check on refund endpoint (Acceptance Criteria #5)** `POST /api/invoices/:id/refund` has no authorization middleware. Manager role must be required. Add role-based auth check — this is a **security issue**. **4. Stats date filter bug (Acceptance Criteria #4)** `startOfMonth` is defined but never used in the stats query — returns all-time data instead of monthly. Pass the date filter to the query. **5. CI checks not running** 0 checks have run. Ensure the branch is rebased/merged with `dev` and CI passes before re-requesting review. ### Required Before Re-Review - [ ] Retarget PR to `dev` - [ ] Resolve merge conflicts - [ ] Add Stripe payment info to InvoiceDetailModal - [ ] Add manager role auth check on refund endpoint - [ ] Fix startOfMonth filter in stats queries - [ ] CI passes green cc @cpfarhood
lint-roller-qa[bot] (Migrated from github.com) reviewed 2026-04-19 01:37:17 +00:00
the-dogfather-cto[bot] (Migrated from github.com) requested changes 2026-04-19 01:40:00 +00:00
the-dogfather-cto[bot] (Migrated from github.com) left a comment

CTO Review — Changes Requested

Two bugs found that need fixing before merge.

Bug 1: Refund stats query sums tip_cents instead of refund amounts

File: apps/api/src/routes/invoices.ts:497-500

const [refundsResult] = await db
  .select({ total: sql<number>`coalesce(sum(tip_cents), 0)` })
  .from(invoices)
  .where(and(eq(invoices.status, "paid"), sql`${invoices.paidAt} >= ${startOfMonth}`));

This sums tip_cents from the invoices table — not refund amounts. The refundsThisMonth stat will show total tips, not total refunds. This should query the refunds table and sum amount_cents (or sum total_cents from invoices that have a stripeRefundId).

Bug 2: getPaymentIntentDetails missing expand — card last4 will never display

File: apps/api/src/services/payment.ts:172

const pi = await stripe.paymentIntents.retrieve(paymentIntentId);

Without expand: ['payment_method'], pi.payment_method is a string ID (e.g. "pm_xxx"), not a Stripe.PaymentMethod object. The cast on line 173 silently fails — .card?.last4 returns undefined, so cardLast4 is always null.

Fix:

const pi = await stripe.paymentIntents.retrieve(paymentIntentId, {
  expand: ['payment_method'],
});

Summary

  • Auth: ✓ (router-level auth + manager role check on refund)
  • Refund UI flow: ✓ (full/partial dialog, error handling, cleanup)
  • Stripe details display: ✓ (UI code is correct, but data will be null due to bug 2)
  • Stats date filter: ✓ (startOfMonth correctly applied)
  • Stats refund calculation: ✗ (bug 1 — wrong column/table)
  • Payment method expand: ✗ (bug 2 — card info always null)

Fix both bugs, then re-request review.

cc @cpfarhood

## CTO Review — Changes Requested Two bugs found that need fixing before merge. ### Bug 1: Refund stats query sums `tip_cents` instead of refund amounts **File:** `apps/api/src/routes/invoices.ts:497-500` ```ts const [refundsResult] = await db .select({ total: sql<number>`coalesce(sum(tip_cents), 0)` }) .from(invoices) .where(and(eq(invoices.status, "paid"), sql`${invoices.paidAt} >= ${startOfMonth}`)); ``` This sums `tip_cents` from the `invoices` table — not refund amounts. The `refundsThisMonth` stat will show total tips, not total refunds. This should query the `refunds` table and sum `amount_cents` (or sum `total_cents` from invoices that have a `stripeRefundId`). ### Bug 2: `getPaymentIntentDetails` missing `expand` — card last4 will never display **File:** `apps/api/src/services/payment.ts:172` ```ts const pi = await stripe.paymentIntents.retrieve(paymentIntentId); ``` Without `expand: ['payment_method']`, `pi.payment_method` is a **string ID** (e.g. `"pm_xxx"`), not a `Stripe.PaymentMethod` object. The cast on line 173 silently fails — `.card?.last4` returns `undefined`, so `cardLast4` is always `null`. **Fix:** ```ts const pi = await stripe.paymentIntents.retrieve(paymentIntentId, { expand: ['payment_method'], }); ``` ### Summary - Auth: ✓ (router-level auth + manager role check on refund) - Refund UI flow: ✓ (full/partial dialog, error handling, cleanup) - Stripe details display: ✓ (UI code is correct, but data will be null due to bug 2) - Stats date filter: ✓ (`startOfMonth` correctly applied) - Stats refund calculation: ✗ (bug 1 — wrong column/table) - Payment method expand: ✗ (bug 2 — card info always null) Fix both bugs, then re-request review. cc @cpfarhood
lint-roller-qa[bot] (Migrated from github.com) approved these changes 2026-04-19 02:01:39 +00:00
the-dogfather-cto[bot] (Migrated from github.com) approved these changes 2026-04-19 02:05:01 +00:00
the-dogfather-cto[bot] (Migrated from github.com) left a comment

CTO Review — Approved

Both bugs from my previous review are correctly fixed in 560d33e:

  1. Refund stats — now queries refunds.amount_cents filtered by refunds.createdAt >= startOfMonth (was incorrectly summing invoices.tip_cents).
  2. Stripe expandexpand: ["payment_method"] added to paymentIntents.retrieve(), so card.last4 populates correctly.

Schema verified: refunds table has both amount_cents (integer) and created_at (timestamp) columns. Import confirmed at invoices.ts:11.

CI: Lint ✓ Typecheck ✓ Test ✓ Build ✓. E2E failure is Docker Compose infra, not code-related.

Merging to dev.

cc @cpfarhood

## CTO Review — Approved Both bugs from my previous review are correctly fixed in `560d33e`: 1. **Refund stats** — now queries `refunds.amount_cents` filtered by `refunds.createdAt >= startOfMonth` (was incorrectly summing `invoices.tip_cents`). 2. **Stripe expand** — `expand: ["payment_method"]` added to `paymentIntents.retrieve()`, so `card.last4` populates correctly. Schema verified: `refunds` table has both `amount_cents` (integer) and `created_at` (timestamp) columns. Import confirmed at `invoices.ts:11`. CI: Lint ✓ Typecheck ✓ Test ✓ Build ✓. E2E failure is Docker Compose infra, not code-related. Merging to `dev`. cc @cpfarhood
the-dogfather-cto[bot] (Migrated from github.com) approved these changes 2026-04-19 02:10:16 +00:00
the-dogfather-cto[bot] (Migrated from github.com) left a comment

CTO Approved

Both previously flagged bugs are fixed:

  1. Refund stats now correctly queries the refunds table (sum(amount_cents), filtered to current month)
  2. Stripe paymentIntents.retrieve now includes expand: ["payment_method"] so card last4 renders

Code review summary:

  • Security: Auth middleware at router level + explicit manager role check on refund endpoint ✓
  • Correctness: Stats date filters applied, refund flow with idempotency ✓
  • Architecture: Clean separation — getPaymentIntentDetails in service layer, UI components well-structured ✓
  • CI: Lint, typecheck, unit tests, build all pass. E2E failure is infra, not code ✓

Merging to dev.

cc @cpfarhood

## CTO Approved Both previously flagged bugs are fixed: 1. Refund stats now correctly queries the `refunds` table (`sum(amount_cents)`, filtered to current month) 2. Stripe `paymentIntents.retrieve` now includes `expand: ["payment_method"]` so card last4 renders Code review summary: - **Security**: Auth middleware at router level + explicit manager role check on refund endpoint ✓ - **Correctness**: Stats date filters applied, refund flow with idempotency ✓ - **Architecture**: Clean separation — `getPaymentIntentDetails` in service layer, UI components well-structured ✓ - **CI**: Lint, typecheck, unit tests, build all pass. E2E failure is infra, not code ✓ Merging to dev. cc @cpfarhood
This repo is archived. You cannot comment on pull requests.