Mobile App · iOS · Android · Web App · Local Gov
CILGD Member Portal
Status
Internal · Under Development
Stack
[ THE THESIS ]
Membership infrastructure fails when payment truth lives on the client.
CILGD replaces paper forms, WhatsApp approval chains, and manual bank transfers with a mobile-first system where registration, dues, and access control resolve through verified server-side events. The architecture treats every Paystack webhook as untrusted until HMAC verification proves it, so membership status is earned by payment truth instead of frontend state.
INTERNAL · UNDER DEVELOPMENT · CILGD · Local governance professional body
SECTION 01 — DESIGN PRINCIPLES
Five constraints that turn a membership body into a Ghana-first platform
Role: Developer & Technology Adviser. CILGD owns the product; I architected and built the technology.
Supabase-first by design
No Express/Django server. Anything the client cannot do runs as an Edge Function (Deno) or a Postgres RPC. Fewer moving parts, smaller blast radius.
RLS on every table · zero trust
Members can only read their own profile, their own documents, their own payments. Admins are elevated through a role column enforced in policies, not in client code.
Payments at the edge · Ghana first
Paystack owns MTN + Telecel MoMo natively — that is the payment rail most international platforms ignore and the one that kills adoption in Ghana. So it ships as the primary rail, not an afterthought.
Offline-resilient onboarding
The 6-step wizard persists state locally so a flaky mobile connection doesn't lose a professional's 10-minute application. Progress is recoverable.
SECTION 02 — SYSTEM TOPOLOGY
Three environments cooperate — one rail leads
Auth
email + Google
Onboarding
6-step wizard
Membership
tiers + status
Payments
Paystack WebView
Settings
themes + profile
Session
@supabase/supabase-js v2 · AsyncStorage + expo-secure-store (iOS Keychain) · 1-hour JWT with auto-refresh
9 key tables
- • profiles · membership_grades
- • fee_schedule · payments
- • documents · courses
- • cpd_entries · events
- • audit_log
6-role RLS
- • super_admin
- • admin
- • finance_admin
- • member · pending_member
- • guest
Deno Edge Fns
- • initialize-payment
- • paystack-webhook (HMAC)
- • pg_cron · renewal checks
- • good-standing downgrades
Paystack · Primary
MTN + Telecel MoMo + cards
Ghana cedi settlement · launch-ready
Flutterwave · Next
Diaspora cards
USD / GBP for UK + US members
pawaPay · Fallback
Pan-African MoMo
KE · UG · RW · NG · more
SECTION 03 — THE SIX-STEP ONBOARDING WIZARD
Every step is persisted to Supabase before the next one loads
A dropped connection in step 4 never wipes steps 1–3.
[ Application State Machine ]
Six gates. One recoverable member profile.
Each screen writes a checkpoint before the next one opens. That makes onboarding feel mobile-native even when the applicant is on an unstable connection.
Persistence
Step-by-step
Failure mode
Resume safely
Personal details
Name, DOB, country, city, nationality, gender, profile photo — PersonalDetailsStep.
Professional details
One of 12 specialisation domains, highest qualification, years of experience, employer, job title — ProfessionalDetailsStep.
Grade selection
System computes eligible tiers from step 2's rules (e.g., Doctorate + 3 years → MCILGD). User picks what they're applying for — GradeSelectionStep.
Ethics Affirmation — mandatory
Digital signature of the CILGD Code of Ethics. profiles.ethics_affirmed = true, ethics_affirmed_at = now(). Cannot be skipped.
Document upload
Ghana Card (front + back), ORC certificate, academic transcripts, professional certifications → RLS-scoped documents bucket.
Review & submit + pay
Read-only snapshot + initial registration fee via Paystack. On success the profile drops into pending_member awaiting admin approval.
SECTION 04 — MEMBERSHIP TIERS
Five tiers · real qualification rules · upgrade_grade runs server-side
Progression is code, not vibes. A Postgres RPC checks the rules and auto-promotes.
SECTION 05 — ENGINEERING HIGHLIGHTS
Three decisions that matter for a government-grade platform
COMPLIANCE
Bank of Ghana 2026 KYC modeled server-side
Admin KYC artefacts (ORC certificate, Ghana Card front + back, recent CILGD bank statement) are first-class objects in the schema, filed to the RLS-scoped documents bucket, readable only by finance_admin and super_admin.
→ regulator-ready out of the box
AUTHORIZATION
Six-role RBAC enforced at the database
Roles are checked in Postgres RLS policies, never in client if-statements. A compromised frontend cannot elevate itself. Finance cannot read raw auth tokens; members cannot read other members.
→ never-in-the-client
PAYMENTS
Signed HMAC webhooks verified inside Deno Edge
Paystack signs every webhook; paystack-webhook verifies the signature before flipping any payments row to success. Replay attacks, forged events, and tampered amounts all fail at the edge.
→ verify before you believe
SECTION 06 — CODE PROOF
The webhook that turns a Paystack charge into a tier promotion
Verbatim Deno Edge Function — signature check first, database writes second.
supabase/functions/paystack-webhook/index.ts
typescript
// supabase/functions/paystack-webhook/index.ts
import { createHmac } from "node:crypto";
Deno.serve(async (req) => {
const raw = await req.text();
const sig = req.headers.get("x-paystack-signature");
const expected = createHmac("sha512", PAYSTACK_SECRET)
.update(raw)
.digest("hex");
if (sig !== expected) {
return new Response("invalid signature", { status: 401 });
}
const event = JSON.parse(raw);
if (event.event === "charge.success") {
// Flip the pending row to success and fire upgrade_grade.
const { data: payment } = await supabase
.from("payments")
.update({ status: "success", paid_at: new Date().toISOString() })
.eq("reference", event.data.reference)
.select()
.single();
if (payment?.fee_type === "tier_renewal") {
await supabase.rpc("upgrade_grade", { user_id: payment.user_id });
}
}
return new Response("ok");
});