Skip to content

Smooth payments [DO NOT MERGE]#49

Open
OPDhaker wants to merge 2 commits intofounder-srm:masterfrom
OPDhaker:smooth-payments
Open

Smooth payments [DO NOT MERGE]#49
OPDhaker wants to merge 2 commits intofounder-srm:masterfrom
OPDhaker:smooth-payments

Conversation

@OPDhaker
Copy link
Copy Markdown
Contributor

@OPDhaker OPDhaker commented Mar 7, 2026

No description provided.

- Implemented API routes for submitting and retrieving payment proofs for teams.
- Created constants for payment-related configurations including UPI IDs and payment amounts.
- Developed utility functions for handling payment proof file validation and storage.
- Added tests for payment-related functionalities to ensure correctness.
- Established database migrations for creating a storage bucket for payment proofs with appropriate access policies.
Copilot AI review requested due to automatic review settings March 7, 2026 05:12
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds an end-to-end “accepted team payment” flow: statement-specific UPI QR display, payment proof upload + status tracking, and an admin queue for reviewing/approving/rejecting proofs.

Changes:

  • Introduces payment domain utilities/constants (statuses, QR config, proof validation, storage paths).
  • Adds user payment-proof upload + download APIs, dashboard UI, and rate limits.
  • Adds admin payment-proof listing/review APIs + admin UI section, plus storage bucket + RLS policies.

Reviewed changes

Copilot reviewed 28 out of 28 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
supabase/migrations/202603070001_insert_dashboard_test_registration.sql Adds a migration that seeds a demo accepted registration (for dashboard testing).
supabase/migrations/202603070002_create_payment_proof_bucket.sql Creates a private Storage bucket and RLS policies for payment proof objects.
src/server/security/rate-limit.ts Adds rate-limit policies for payment proof upload/view (IP + user).
src/server/registration/service.ts Preserves payment metadata during patch flows and removes payment proof objects on team deletion.
src/server/registration/payment-proofs.ts Implements submit + signed-download URL services for team payment proofs.
src/server/registration/mappers.ts Maps payment metadata fields from registration details into TeamRecord.
src/server/registration/mappers.test.ts Adds tests for mapping and dropping invalid payment metadata.
src/server/mass-mail/team-decision.ts Updates decision emails to link directly to team dashboard and includes payment instructions; adds required teamId.
src/server/mass-mail/team-decision.test.ts Updates tests for new decision email content/linking.
src/server/admin/review-submissions.ts Passes teamId into decision mail sender.
src/server/admin/review-submissions.test.ts Updates expectations for decision mail payload including teamId.
src/server/admin/payment-proofs.ts Adds admin services to list payment proofs, update payment decisions, and generate signed URLs.
src/server/admin/payment-proofs.test.ts Adds tests for listing and decision updates (approve/reject reason required).
src/lib/register-schema.ts Extends team record schema/types to include payment metadata + payment status enum.
src/lib/register-schema.test.ts Adds schema tests for valid/invalid payment metadata in team records.
src/lib/payments.ts Adds payment statuses, QR config builder, and payment proof validation/path helpers.
src/lib/payments.test.ts Tests QR config coverage and status values.
src/lib/payment-constants.ts Adds constants for INR amount and per-statement UPI IDs (currently defaulted).
src/app/dashboard/[teamId]/page.tsx Adds dashboard payment section (QR generation, UTR + proof upload, ticket unlock gating).
src/app/api/register/[teamId]/payment-proof/route.ts Adds authenticated GET (redirect to signed URL) and POST (multipart upload) endpoints.
src/app/api/register/[teamId]/payment-proof/route.test.ts Adds route tests for CSRF, validation, upload success, and GET redirect/error paths.
src/app/api/admin/payment-proofs/route.ts Adds admin GET endpoint for payment-proof queue with statement filtering.
src/app/api/admin/payment-proofs/route.test.ts Adds tests for auth/admin enforcement and statement filter validation.
src/app/api/admin/payment-proofs/[teamId]/route.ts Adds admin PATCH endpoint to approve/reject payment proofs (CSRF + JSON validation).
src/app/api/admin/payment-proofs/[teamId]/route.test.ts Adds tests for CSRF failure, payload validation, and success path.
src/app/api/admin/payment-proofs/[teamId]/file/route.ts Adds admin GET endpoint to redirect to signed proof URL (auth + admin + rate limits).
src/app/api/admin/payment-proofs/[teamId]/file/route.test.ts Adds tests for redirect and unauthenticated forwarding.
src/app/admin/admin-review-client.tsx Adds admin “Payment Queue” section and navigation, plus approve/reject interactions.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +1 to +85
-- Seeds a fake accepted registration for the maintainer account so the
-- dashboard can be exercised end-to-end in local/dev environments.
--
-- Assumption: the target auth account uses opdhaker2007@gmail.com.
-- If your local Supabase auth user uses a different email, update the value
-- below before applying this migration.
do $$
declare
v_target_email constant text := 'opdhaker2007@gmail.com';
v_target_user_id uuid;
begin
select users.id
into v_target_user_id
from auth.users as users
where lower(users.email) = lower(v_target_email)
order by users.created_at desc
limit 1;

if v_target_user_id is null then
raise notice
'Skipping dashboard test registration seed because no auth.users row exists for %.',
v_target_email;
else
delete from public.eventsregistrations
where event_id = '325b1472-4ce9-412f-8a5e-e4b7153064fa'::uuid
and application_id = v_target_user_id
and coalesce(details ->> 'isTestEntry', 'false') = 'true';

insert into public.eventsregistrations (
event_id,
event_title,
application_id,
details,
registration_email,
is_team_entry,
is_approved
)
values (
'325b1472-4ce9-412f-8a5e-e4b7153064fa'::uuid,
'Foundathon 3.0',
v_target_user_id,
jsonb_build_object(
'teamType', 'non_srm',
'teamName', 'Dashboard Demo Team',
'collegeName', 'Foundathon Test University',
'isClub', true,
'clubName', 'Founders Club QA',
'lead', jsonb_build_object(
'name', 'Uttam Demo',
'collegeId', 'FTU-LEAD-001',
'collegeEmail', v_target_email,
'contact', 9876543210
),
'members', jsonb_build_array(
jsonb_build_object(
'name', 'Demo Member One',
'collegeId', 'FTU-MEM-002',
'collegeEmail', 'member.one@example.com',
'contact', 9876543211
),
jsonb_build_object(
'name', 'Demo Member Two',
'collegeId', 'FTU-MEM-003',
'collegeEmail', 'member.two@example.com',
'contact', 9876543212
)
),
'problemStatementCap', 15,
'problemStatementId', 'ps-03',
'problemStatementLockedAt', '2026-03-07T00:00:00.000Z',
'problemStatementTitle', 'Localized AI Skills Training Platform',
'presentationFileName', 'dashboard-demo-submission.pptx',
'presentationFileSizeBytes', 1048576,
'presentationMimeType', 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
'presentationPublicUrl', 'https://example.com/foundathon/dashboard-demo-submission.pptx',
'presentationStoragePath', 'registrations/dashboard-demo/submission.pptx',
'presentationUploadedAt', '2026-03-07T00:00:00.000Z',
'isTestEntry', true,
'testEntryLabel', 'dashboard-demo'
),
v_target_email,
true,
'ACCEPTED'
);
end if;
Copy link

Copilot AI Mar 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This migration seeds a fake accepted registration into public.eventsregistrations. Supabase migrations are typically applied to every environment (including production), so this will permanently insert test/PII data (hard-coded personal email, dummy phone numbers, etc.) into real databases. Please move this to a dev-only seed mechanism (e.g., a separate seed.sql/manual script) or gate it behind an explicit, environment-specific process rather than a numbered migration.

Suggested change
-- Seeds a fake accepted registration for the maintainer account so the
-- dashboard can be exercised end-to-end in local/dev environments.
--
-- Assumption: the target auth account uses opdhaker2007@gmail.com.
-- If your local Supabase auth user uses a different email, update the value
-- below before applying this migration.
do $$
declare
v_target_email constant text := 'opdhaker2007@gmail.com';
v_target_user_id uuid;
begin
select users.id
into v_target_user_id
from auth.users as users
where lower(users.email) = lower(v_target_email)
order by users.created_at desc
limit 1;
if v_target_user_id is null then
raise notice
'Skipping dashboard test registration seed because no auth.users row exists for %.',
v_target_email;
else
delete from public.eventsregistrations
where event_id = '325b1472-4ce9-412f-8a5e-e4b7153064fa'::uuid
and application_id = v_target_user_id
and coalesce(details ->> 'isTestEntry', 'false') = 'true';
insert into public.eventsregistrations (
event_id,
event_title,
application_id,
details,
registration_email,
is_team_entry,
is_approved
)
values (
'325b1472-4ce9-412f-8a5e-e4b7153064fa'::uuid,
'Foundathon 3.0',
v_target_user_id,
jsonb_build_object(
'teamType', 'non_srm',
'teamName', 'Dashboard Demo Team',
'collegeName', 'Foundathon Test University',
'isClub', true,
'clubName', 'Founders Club QA',
'lead', jsonb_build_object(
'name', 'Uttam Demo',
'collegeId', 'FTU-LEAD-001',
'collegeEmail', v_target_email,
'contact', 9876543210
),
'members', jsonb_build_array(
jsonb_build_object(
'name', 'Demo Member One',
'collegeId', 'FTU-MEM-002',
'collegeEmail', 'member.one@example.com',
'contact', 9876543211
),
jsonb_build_object(
'name', 'Demo Member Two',
'collegeId', 'FTU-MEM-003',
'collegeEmail', 'member.two@example.com',
'contact', 9876543212
)
),
'problemStatementCap', 15,
'problemStatementId', 'ps-03',
'problemStatementLockedAt', '2026-03-07T00:00:00.000Z',
'problemStatementTitle', 'Localized AI Skills Training Platform',
'presentationFileName', 'dashboard-demo-submission.pptx',
'presentationFileSizeBytes', 1048576,
'presentationMimeType', 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
'presentationPublicUrl', 'https://example.com/foundathon/dashboard-demo-submission.pptx',
'presentationStoragePath', 'registrations/dashboard-demo/submission.pptx',
'presentationUploadedAt', '2026-03-07T00:00:00.000Z',
'isTestEntry', true,
'testEntryLabel', 'dashboard-demo'
),
v_target_email,
true,
'ACCEPTED'
);
end if;
-- NOTE: This migration was originally used to seed a fake accepted
-- registration for dashboard testing in local/dev environments.
--
-- Seeding test/demo data via numbered Supabase migrations is unsafe because
-- those migrations are typically applied to every environment, including
-- production. That would permanently insert test/PII-like data into real
-- databases.
--
-- To create dashboard demo data, copy the appropriate INSERT statement(s)
-- into a separate, dev-only seed script (for example, a local `seed.sql`
-- file or a manual psql script) and run it explicitly only in non-prod
-- environments.
--
-- This migration is intentionally a no-op to avoid seeding data anywhere.
do $$
begin
raise notice
'Migration 202603070001_insert_dashboard_test_registration: no-op by design. '
'Use a separate dev-only seed script to create dashboard test registrations.';

Copilot uses AI. Check for mistakes.
Comment on lines +4 to +9
-- Assumption: the target auth account uses opdhaker2007@gmail.com.
-- If your local Supabase auth user uses a different email, update the value
-- below before applying this migration.
do $$
declare
v_target_email constant text := 'opdhaker2007@gmail.com';
Copy link

Copilot AI Mar 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The hard-coded target email (opdhaker2007@gmail.com) looks like real PII and will be written into whichever database applies this migration. If you keep any kind of seeding here, avoid real emails and prefer an obviously fake/example domain (or derive the user by a non-PII identifier).

Suggested change
-- Assumption: the target auth account uses opdhaker2007@gmail.com.
-- If your local Supabase auth user uses a different email, update the value
-- below before applying this migration.
do $$
declare
v_target_email constant text := 'opdhaker2007@gmail.com';
-- Assumption: the target auth account uses dashboard.maintainer@example.com.
-- If your local Supabase auth user uses a different email, update the value
-- below before applying this migration.
do $$
declare
v_target_email constant text := 'dashboard.maintainer@example.com';

Copilot uses AI. Check for mistakes.
Comment on lines +3 to +8
export const PAYMENT_AMOUNT_INR = 300;
export const DEFAULT_PAYMENT_UPI_ID = "9301161940@fam";

export const PAYMENT_UPI_ID_BY_STATEMENT = Object.fromEntries(
PROBLEM_STATEMENTS.map((statement) => [statement.id, DEFAULT_PAYMENT_UPI_ID]),
) as Record<(typeof PROBLEM_STATEMENTS)[number]["id"], string>;
Copy link

Copilot AI Mar 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

DEFAULT_PAYMENT_UPI_ID is hard-coded to a specific UPI/VPA. If this repo is shared beyond a private ops context, this can unintentionally expose real payment routing info and makes rotations/environment differences harder. Consider sourcing this from environment/config (or Supabase event settings) and keeping only non-sensitive defaults in code.

Copilot uses AI. Check for mistakes.
}

if (!isPaymentProofExtensionAllowed(fileName)) {
return fail("Only PNG, JPG, WEBP, or PDF payment proofs are allowed.", 400);
Copy link

Copilot AI Mar 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The extension allowlist includes .jpeg, but this error message omits JPEG (it says PNG/JPG/WEBP/PDF only). To avoid confusing users, update the message to include JPEG as an allowed type (or drop .jpeg from the allowlist if it’s not intended).

Suggested change
return fail("Only PNG, JPG, WEBP, or PDF payment proofs are allowed.", 400);
return fail(
"Only PNG, JPG/JPEG, WEBP, or PDF payment proofs are allowed.",
400,
);

Copilot uses AI. Check for mistakes.
Comment on lines +35 to +49
using (
bucket_id = 'foundathon-payment-proofs'
and (storage.foldername(name))[1] = 'payment-proofs'
and (storage.foldername(name))[2] = auth.uid()::text
);

create policy "payment proof owners can upload"
on storage.objects
for insert
to authenticated
with check (
bucket_id = 'foundathon-payment-proofs'
and (storage.foldername(name))[1] = 'payment-proofs'
and (storage.foldername(name))[2] = auth.uid()::text
);
Copy link

Copilot AI Mar 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The storage RLS predicates only constrain the first two path segments (payment-proofs/<uid>/...), which still allows an authenticated user to upload arbitrary objects under their UID folder (unlimited filenames/subfolders) and use this bucket as general-purpose storage. If this bucket is meant strictly for one proof per team, consider tightening the policy to an exact key pattern (e.g., regex/LIKE enforcing payment-proofs/<uid>/<teamId>/proof.<ext> and allowed extensions) to reduce storage abuse risk.

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants