Back
Backend Engineering

PNTSH

PNTSH architecture diagram

Summary

(6 min read)

PNTSH is a Laravel 12 API powering a group-based contributions product. It supports group creation and membership, invite flows, fee creation, member payments via Paystack, and owner withdrawals with OTP-verified payout accounts — with notifications delivered via email and push.

Project Snapshot

My Role

Backend Developer

Duration

2026

Context

Backend API Project

Outcome

Group invites + fee payments · Paystack verification webhooks · OTP-verified withdrawals

Stack

Laravel 12PHPMySQLLaravel SanctumFilamentPaystackHubtel SMSFirebase FCMLaravel Mail

The Problem

Context

Group contributions need simple, reliable flows: create a group, invite members, collect payments, and let owners withdraw funds safely — all while keeping accounting accurate.

The Pain

Without strong payment verification, idempotent callbacks, and safe withdrawal controls, financial flows become inconsistent and risky. Small edge cases (duplicate webhooks, partial payments, unverified payout accounts) cause big issues.

Why It Mattered

Payments must be trustworthy and auditable. Users need confidence that their contributions are recorded correctly, and group owners need safe withdrawals with clear checks and balances.

Goals & Requirements

Technical Goals

  • Secure auth for mobile clients (Sanctum)
  • Group + membership management with ownership controls
  • Two invite modes: direct invites and invite links
  • Fee creation and tracking (active/closed) with accurate totals
  • Payments via Paystack (init + verify + webhook handling)
  • Withdrawals via Paystack transfers with OTP verification
  • Notifications via email + push (FCM)
  • Admin operations via Filament

Constraints

  • Design for webhook retries (idempotency)
  • Keep accounting updates consistent and transaction-safe
  • Treat withdrawals as high-risk actions with extra verification

Architecture Design

A layered Laravel API (routes → controllers → services → Eloquent/MySQL) with dedicated modules for groups, invites, fees, payments, withdrawals, and notifications. Payments and withdrawals integrate with Paystack, payout account verification uses Hubtel SMS OTP, and notifications fan out to email + Firebase FCM.

Architecture Diagram

Scroll horizontally on smaller screens to view full diagram

Component Breakdown

Auth (Laravel Sanctum)

Secures API access for mobile clients and admins.

Groups & Membership

Group creation, membership joins, and ownership permissions.

Invites

Direct invites and invite links for onboarding members into groups.

Fees

Fee lifecycle (create, active/closed) and aggregate totals for collection/withdrawal.

Payments (Paystack)

Initialize and verify fee payments and record durable payment state transitions.

Withdrawals + Payout Accounts

OTP verification for payout accounts and Paystack transfers for withdrawals.

Notification Service

Triggers push notifications (FCM) and email updates for key events.

Admin Panel (Filament)

Operational visibility and moderation of groups, fees, payments, and withdrawals.

MySQL

Primary datastore for users, groups, fees, payments, and withdrawals.

Key Design Decisions

Service layer for business logic

Keeps controllers thin and concentrates payment/withdrawal rules in testable, reusable services.

Webhook verification + idempotency mindset

Paystack can retry events; the API must safely handle duplicate callbacks without double-counting payments.

OTP for withdrawal account verification

Withdrawals are high-risk; requiring OTP via SMS reduces fraud and prevents misdirected payouts.

Filament for admin tooling

Accelerates internal dashboards and operations without building a separate admin frontend.

Implementation Breakdown

01

Domain modules and routing

API routes are organized around the product’s core modules (groups, invites, fees, payments, withdrawals).

  • Clear REST endpoints for group lifecycle and membership
  • Separate flows for direct invites vs invite links
  • Admin routes exposed via Filament
System flow (high-level)mermaid
Rendering diagram…
View diagram source
flowchart TB
    subgraph LaravelAPI[Laravel API]
      auth[Auth & Sanctum]
      groups[Groups & Membership]
      invites[Invites/Invite Links]
      fees[Group Fees]
      payments[Payments]
      withdrawals[Withdrawals]
      notify[Notification Service]
      adminc[Admin/Filament Modules]
    end

    db[(MySQL)]
    paystack[Paystack]
    hubtel[Hubtel SMS]
    fcm[Firebase]
    mail[SMTP]

    auth --> db
    groups --> db
    invites --> db
    fees --> db
    payments --> db
    withdrawals --> db
    adminc --> db

    payments --> paystack
    withdrawals --> paystack
    withdrawals --> hubtel
    notify --> fcm
    notify --> mail
02

Payments: init → verify → finalize

Payments are created as PENDING, verified against Paystack, then finalized with consistent DB updates.

  • Store Paystack reference per payment
  • Verify on callback/webhook before marking SUCCESS
  • Update fee totals after successful verification
Payment + withdrawal sequencemermaid
Rendering diagram…
View diagram source
sequenceDiagram
    participant U as User (Member)
    participant API as Laravel API
    participant Pay as Paystack
    participant DB as Database
    participant O as Group Owner

    U->>API: POST /groups/{groupId}/fees/{feeId}/pay
    API->>DB: Create/Update payment (PENDING, reference)
    API->>Pay: Initialize transaction
    Pay-->>API: authorization_url
    API-->>U: payment link/reference

    Pay-->>API: Webhook charge.success
    API->>Pay: Verify transaction
    Pay-->>API: success
    API->>DB: Update payment=SUCCESS, paid_at
    API->>DB: Update fee totals (total_collected, total_paid_members)

    O->>API: POST /withdrawal/request-otp
    API->>O: OTP sent (Hubtel SMS)

    O->>API: POST /withdrawal/verify-otp
    API->>Pay: Create transfer recipient
    API->>DB: Save withdrawal_account(recipient_code)

    O->>API: POST /group/{groupId}/fee/{feeId}/withdraw-funds
    API->>DB: Validate owner, closed fee, available amount
    API->>Pay: Transfer funds
    Pay-->>API: transfer reference/status
    API->>DB: Insert group_withdrawals
    API->>DB: Increment group_fees.total_withdrawn (on success)
    API-->>O: Withdrawal result
03

Withdrawals with safety checks

Withdrawals validate ownership, fee closure rules, and available balance before initiating a transfer.

  • OTP-verifies payout account before enabling withdrawals
  • Records group_withdrawals and updates total_withdrawn
  • Rejects withdrawals when fee is not closed or balance is insufficient
04

Notifications

Event-driven notifications keep members informed about invites, fees, payments, and withdrawals.

  • Push notifications via Firebase FCM
  • Email notifications via Laravel Mail

Challenges & Solutions

#1Preventing double-counted payments

The Problem

Payment providers may send duplicate webhooks, and users may retry checkout. Without protections, totals can drift.

The Fix

Model payments as a state machine (PENDING → SUCCESS/FAILED) and verify against Paystack before finalizing. Treat webhook handling as idempotent and only apply fee total updates once per reference.

#2Withdrawal safety and verification

The Problem

Allowing withdrawals without strong verification can lead to misdirected funds or fraud.

The Fix

Require OTP verification (Hubtel SMS) to create/activate a payout recipient, then perform Paystack transfers only for verified accounts.

Results & Impact

A complete backend foundation for group contributions with reliable payment verification and safe withdrawal flows.

Before → After

Payment verification

Unverified callbacksVerified against Paystack
Trustworthy state

Withdrawal safety

No secondary verificationOTP-verified payout account
Reduced risk

Operations

No admin toolingFilament admin panel
Faster support

Business Outcome

Members can pay fees confidently, owners can withdraw safely, and admins can monitor activity with clear records for support and auditing.

Reflections

Would Do Differently

  • 01Add explicit idempotency keys and DB-level uniqueness constraints for provider references
  • 02Expand automated tests around webhook edge cases and partial failures

Key Takeaways

  • 01In payment systems, correctness beats cleverness — verify, record, and reconcile
  • 02Withdrawals deserve a higher security bar than deposits

Next Project

Event-Crypt

Full-Stack Engineering

Event-Crypt

Thanks for Reading