Technical System Documentation · iSolveAfrica Ltd

Raptors Freight and Courier Service
Web Application — System Workflow

Developer reference for the Raptors platform. Every operational phase — identity verification, reference issue, pricing, payment, USPS label issue, warehouse intake and Scan and Price, carriage, break-out, rider delivery, auction and settlement — with API calls, database writes, state transitions, notification triggers and financial postings documented at each step.

Stack
PHP 8.4 · MySQL 8.x · Bootstrap 5
API base
/api/v1/ — REST, Bearer token
References
RFC · RAP · RSP · RCP
Payments
Paystack · Hubtel
Document
RFCS-WEB-SOW-2026-004 v4.0

What this document is. The Scope of Work states what is being built. This states how it behaves at runtime — the sequence a developer implements and a tester verifies. Read them together.

Conventions. new means the capability does not exist today. upgrade means it exists and is being rebuilt. Endpoint paths are relative to /api/v1/. Every state change writes to package_events and to activity_log, and surfaces on the tracking page and the public API.

Duration
2 months
8 weeks, parallel build
Payments
Paystack + Hubtel
Card · momo · bank transfer
Warehouses
2
US origin · Ghana destination
Currencies
GHS + USD
Shown together throughout
Rider commission
10–15%
Configurable per rider or zone
Foundation

Reference Numbering new

Four prefixes, four independent sequences. The prefix is the primary key the whole system reasons about — it appears on labels, in messages, on the tracking page and in every API response.

RFC
Raptors Freight Customer — locker ID, issued on account approval
RAP
Raptors Air Package
RSP
Raptors Sea Package
RCP
Raptors Courier Package — courier and USPS parcels carrying an in-house label
0.1

Generation Rules

Implementation

  • One sequence table per prefix, allocated inside a transaction to prevent collision under load
  • Sequences must be non-guessable — a customer must not be able to enumerate other customers' packages by incrementing a number in the tracking box
  • Reference is immutable once issued
  • Stored as prefix + sequence in separate columns; concatenated for display

Behaviour

  • The tracking box resolves any of the four prefixes, plus USPS numbers and consolidation references, on one endpoint
  • A package moving between service lines (sea to air, say) keeps its original reference and writes a service-change row to the activity log — history is never orphaned
  • The prefix is emitted on every label, invoice, waybill, manifest, POD and API response
Phase One

Identity Verification & Accounts new

Verification happens on the site with the Ghana Card. No account prices, books or pays before approval. The RFC locker ID is issued at the moment of approval.

1.1

Account State Machine

Registered Email Verified Documents Submitted Pending Approval Approved + RFC issued | Rejected Re-submitted
Hard gate. One middleware guard blocks quote commitment, POST /packages, POST /payments and POST /labels/usps for any account not approved. The customer may browse prices and use the public calculator, but cannot commit. Attempts return 403 account_not_verified with a deep link. Implement this once, centrally — four separate checks is how one gets missed.
1.2

Submission & Review

Captured

  • Ghana Card number, validated against the GHA-XXXXXXXXX-X format
  • Front and back images — JPG, PNG or PDF
  • Alternatives: passport, driver's licence, voter ID
  • Proof of address dated within 3 months
  • Business: certificate of incorporation, TIN, VAT certificate

API & storage

  • POST/me/verification
  • PATCH/admin/verification/{id}
  • Card number encrypted at column level; images outside the web root
  • Served only via short-lived signed URLs — no public path emitted
  • On approve: RFC issued, virtual address issued, WhatsApp + email fired, guards lift
1.3

Incognito Account View new

Behaviour

  • Staff open a client account and render the client's own views — dashboard, packages, prices, documents
  • Session carries an impersonating_customer_id alongside the real user_id
  • Persistent banner naming the client being viewed
  • Time-limited; expires back to the staff session

Constraints

  • Strictly read-only. Every write path checks for an impersonation flag and refuses. Do not rely on hiding buttons — enforce at the controller
  • Session start and end both written to activity_log with staff member, client and duration
  • Incognito cannot be nested, and cannot be entered on another staff account
Phase Two

Pricing Engine new

One engine serves the public calculator, item pages, the booking wizard, warehouse Scan and Price, the admin and the public API. A quote and a booking cannot disagree because they run the same code path.

2.1

Resolution Order

Customer special price Agency price Pre-priced item Weight band Volumetric / volume Scan and Price
First match wins, then add-ons apply. Customs charge (by product category and declared value), handling and delivery are added on top of whichever rule fired. The rule that fired is stored as price_source on the quote, so any price on the platform can be explained after the fact — needed for both customer disputes and Financial Sheet margin analysis.
2.2

Scan and Price new

Trigger

  • Fires only when the engine returns price_source = null at intake — not in the item catalogue and weight pricing does not apply
  • Package enters an Awaiting Pricing queue and cannot progress to consolidation
  • POST/warehouse/{node}/scan-price

Screen & effect

  • Shows intake photograph, weight, dimensions, category and a suggested price from nearest-match history
  • Price flows straight to the invoice; customer notified on WhatsApp and email
  • Officer can save the item into the pre-priced catalogue so it auto-prices next time
  • Manual price is written with the officer's identity and stored as price_source = scan_and_price
2.3

Categories, Currency & Discounts

Product categories new

CategoryRule effect
General GoodsStandard customs and handling
Dangerous GoodsDeclaration required; air carriage blocked until cleared
ElectronicsDeclared-value scrutiny; intake photo required; 2"×1" label option
Car PartsOversize handling; separate customs rates
  • Consolidation exports weight and count per category — the figure customs and carriers ask for

Currency

  • Rate set in admin; written onto the quote record at quote time
  • A price never moves retrospectively when the admin rate changes

Discounts new

  • Fixed amount or percentage; per invoice or standing per customer
  • Above threshold requires approval; approver recorded
  • Rendered as its own invoice line and tracked separately on the Financial Sheet — discounting stays visible rather than hiding inside a lower headline price
Phase Three

Payments — Paystack & Hubtel new

Two providers on the same networks, plus Hubtel bank transfer. Both normalise to one internal payment-event model, so nothing downstream knows which provider handled a transaction.

3.1

Initiation, Confirmation & Failover

Sequence

  • POST/payments/initiate with a client-generated idempotency key
  • Router selects the provider on channel, currency, amount band and network
  • Insert payment_attempts with status=pending
  • The provider callback is the source of truth — never the browser redirect
  • Callback signature verified before any state change; replays rejected and logged
  • Sweeper re-queries anything pending beyond 15 minutes

Failover

  • Health monitor tracks success rate and latency per provider on a rolling window; a breaching provider is demoted
  • Failed or timed-out initiation retries once on the other provider, transparently
  • The idempotency key travels with the retry — a switchover can never double-charge
  • Admin can pin all traffic to one provider during a known outage
  • Every routing decision, retry and switchover is logged on the payment trail
3.2

Split Payments new

Model

  • An invoice holds many payments rows, each with its own method, provider, reference, amount, currency and date
  • Invoice state derives from the sum: unpaidpart_paidpaid
  • Never store a single amount_paid field — derive it, or the two go out of step the first time a refund lands

Surfacing

  • Running balance shown after each payment, on the invoice and to the customer
  • Each split payment appears individually on the Financial Sheet
  • Release and clearance rules read the derived balance, not the last payment
3.3

Collection at the Door

Rider taps Collect Hubtel prompt to recipient wallet Recipient approves Callback received Package released, POD unlocked
Cash path. Cash is recorded against the rider in rider_collections and the package releases immediately. That balance stays open until banked and cleared at end-of-day cash-up, reconciling into the Financial Sheet. A rider with an unreconciled balance above a configurable ceiling cannot be assigned new collect-on-delivery jobs.
Phase Four

USPS Labels — Request, Pay, Print new

Real-time USPS pricing, payment on the site, and a printable label in three sizes — without the customer leaving Raptors.

4.1

Sequence & Ordering Rules

Request Address standardised Live USPS price Customer pays Label purchased Printable Carrier pickup Tracking mirrored At US warehouse
Two ordering rules that cost real money if broken. Address standardisation runs before purchase — a malformed address produces a paid label that cannot be inducted, and the USPS refund window is short. Label purchase runs after payment clears — postage draws from the Raptors Enterprise Payment Account, so buying before collection puts Raptors out of pocket on every abandoned request.
4.2

USPS Services & Label Output

Endpoints consumed

  • OAuth 2.0 — client credentials; key and secret in the encrypted secrets store
  • Domestic Prices — live rate for the request screen and calculator
  • Domestic Labels v3 — purchase; image decoded and stored
  • Carrier Pickup — schedule collection at the customer address
  • Scan Form — one scan accepts a whole day's batch
  • Tracking — poll and mirror scans onto the tracking page
  • Rate re-checked at purchase; drift beyond tolerance re-quotes rather than silently absorbing the difference

Label sizes new

SizeUse
4"×6" thermalStandard packages; Zebra / Dymo, also PDF
A4 / LetterOffice or home printer, 2-up and 4-up
2"×1"Phones, tablets, small gadgets — peels off cleanly without damaging the item or its retail box
QRCounter printing at a US Post Office
  • Size auto-selected from product category and package dimensions; staff override available
  • In-house Raptors label carrying the RCP reference printed alongside or instead of the USPS label
  • Reprints increment a counter and log — never mistaken for a second purchase
  • Label cost posts automatically to cost_lines
External dependency. Live purchase needs a USPS Ship enrolment and a funded USPS Enterprise Payment Account held by Raptors, plus USPS approval. Applications lodge in week 1 because the approval clock is not ours. Build against the USPS test service — watermarked, unbilled labels — so go-live is a credential switch, not a rebuild.
Phase Five

Warehouse — US Origin & Ghana Destination new

One engine, two locations, separate stock. Every physical movement writes a ledger row, so stock can always be explained rather than merely counted.

5.1

Intake

Scan and match

  • Barcode scan of carrier label, RCP label or pre-alert reference; manual entry as fallback
  • Auto-match to a pre-alert on carrier tracking number
  • POST/warehouse/{node}/intake
  • No match → unidentified_stock with photographs, awaiting claim

Capture & consequence

  • Actual weight and dimensions; category confirmed or corrected
  • Variance beyond tolerance against declared figures re-prices automatically and re-issues the quote
  • Not auto-priceable → Awaiting Pricing queue → Scan and Price (2.2)
  • Photographs mandatory; video where condition needs recording; annotated photo required on damage → damage_register
  • Shelf location assigned; location label printed; status → In Warehouse; storage clock starts after the free period
5.2

Release — Pay Before Clearance new

Release requested Derived balance checked Zero → released | Outstanding → blocked Admin override + reason Released
The gate reads the derived balance from all split payments and discounts, not a stored field and not the last payment. Overrides require a recorded reason, a second approver, and are written to the activity log. This is the single control that stops uncollected revenue leaving the building.
5.3

Stock & Ageing

Inventory

  • Live stock by node, customer, category, location and age
  • Bin transfer as a scan-out / scan-in pair, both ledgered
  • Stock counts by zone or customer with a variance report
  • Repack with before/after photographs and a repack charge

Ageing

  • Alerts at 7 / 14 / 30 / 60 days, each notifying the customer
  • Storage charges accrue daily to revenue_lines — visible before the package ships, billed on release
  • Ready-for-collection date recorded here starts the 3-week auction clock (Phase Nine)
Phase Six

Carriage & Consolidation

Air packages carry full waybill handling. Sea and courier packages are handled at package level. All three feed one consolidation engine.

6.1

Status Lifecycles

Air package (RAP)

BookedManifested DepartedIn Transit ArrivedCustoms ReleasedOut for DeliveryDelivered

Consolidation — tracked separately from its packages

OpenClosed ManifestedDeparted In TransitArrived Break-outClosed-Out
The invariant. A package transitions only within the bounds of its parent consolidation — a package cannot read Delivered while its consolidation is still In Transit. Both statuses show on the tracking page so the two levels are understood rather than conflated.
6.2

Manifest & Category Export new

Air documents

  • Master and House Air Waybill, printed with barcode
  • Airline, flight number, ETD/ETA, IATA airport codes
  • Dangerous Goods declaration required; air carriage blocked until cleared
  • cost_lines: airline charge, duty and levies paid on behalf

Category breakdown

  • Consolidation manifest exports total weight and package count per category — General Goods, Dangerous Goods, Electronics, Car Parts
  • Export formats: Excel and PDF, matching the layout customs and carriers expect
  • Any package with an unconfirmed category blocks the export — the number has to be right
6.3

Break-out at the Ghana Node

What happens

  • Consolidation → Break-out
  • One delivery_jobs row per recipient, carrying the package list, address, contacts and derived outstanding balance
  • WhatsApp broadcast to every recipient — each message lists only that recipient's own packages
  • Ready-for-collection date set; auction clock starts

Financial effect

  • cost_lines: duty and levies, terminal handling, destination haulage
  • revenue_lines: clearance service fee, accrued storage
  • Any package whose margin turns negative here is flagged on the Financial Sheet variance queue
Phase Seven

Last-Mile, Riders & Commission new

Deliveries assigned to riders with a price on each job, a Raptors commission of 10–15%, and a rider view showing the delivery cost on every package.

7.1

Assignment, Pricing & Commission

Dispatcher

  • All unassigned deliveries with zone, weight, category and value
  • Assign singly or build a route; capacity checked by weight and volume
  • POST/routes/{id}/assign
  • Bulk scan-to-load — a package physically cannot be loaded onto the wrong route

Fee and commission

  • Delivery fee defaults from the zone; overridable per job with the dispatcher and reason recorded
  • Written to delivery_jobs.rider_fee at assignment and frozen
  • Commission 10–15% calculated at the same moment into delivery_jobs.commission_rate and commission_amount; configurable per rider or per zone
  • Freezing both at assignment means a later rate change never rewrites history — rider statements stay reproducible
  • Net rider cost posts to cost_lines; commission posts as Raptors revenue
7.2

Riders Page

What the rider sees

  • Assigned packages, each showing its delivery cost
  • Amount to collect from the recipient, where any
  • Daily total — deliveries, gross earnings, commission deducted, net due
  • Earnings history and settlement statement
  • One tap to navigate to the address

Technical

  • GET/riders/me/manifest — jobs, fees, commission, collect amounts
  • GET/riders/me/earnings
  • Mobile-responsive web — no app install
  • Manifest cached; completed stops queue locally and sync when signal returns
  • Fee and commission visibility scoped to the rider's own jobs — a rider never sees another rider's rates
7.3

At the Door

Proof of delivery

  • Recipient name and relationship to the addressee
  • On-screen signature; mandatory delivery photograph
  • Video capture for high-value parcels or disputed handovers
  • Delivery PIN by SMS for high-value parcels
  • GPS and timestamp bound to the event
  • POD PDF sent to the customer on WhatsApp and email; image and video attached to the tracking page

Failure handling

  • Reasons: absent · address not found · refused · payment declined · no access
  • Automatic re-delivery scheduling with a charge rule and an attempt ceiling
  • Ceiling reached → return to warehouse, customer notified, storage clock restarts, auction clock restarts
  • Failure analysis by zone drives which address field to prompt for at booking
Address quality. GhanaPost GPS digital address, landmark note and map pin are all captured at booking. Ghanaian street addressing is inconsistent, and capturing all three is the highest-leverage single intervention on first-attempt delivery success.
Phase Eight

Financial Sheet & Debt new

Every phase above posts here automatically. Nothing is typed in twice, and margin is known before delivery rather than after month end. Visible to Administration, Finance and Customer Service.

8.1

What Posts, and From Where

Posted byRevenue linesCost lines
Pricing engine (2)Freight, weight charge, customs charge, handling
Discounts (2.3)Negative revenue line, separately tracked
USPS labels (4)Label handling feeUSPS postage, carrier collection cost
Warehouse (5)Storage charges, repack charge
Air carriage (6)Freight, fuel and security surchargeAirline charge, duty and levies paid on behalf
Break-out (6.3)Clearance service feeTerminal handling, destination haulage
Last mile (7)Delivery fee, re-delivery charge, rider commissionRider fee per job
Payments (3)Provider processing fees
NotificationsWhatsApp, SMS and media unit costs
Auctions (9)Auction proceedsWrite-off on shortfall
Immutable. Entries are never edited. A correction is a reversal with a linked contra entry, so the sheet always reconstructs how it reached its current position. Every line drills through to the package, invoice, customer and payment behind it.
8.2

Debt & Reconciliation

Debt management new

  • Balance per customer, aged into buckets, with contact details and last-reminder date
  • One click fires a reminder on WhatsApp and email
  • Debt threshold blocks new bookings until cleared; override requires approval and a reason
  • Customer management page surfaces total paid and total debt on the client list itself

Reconciliation

  • Paystack and Hubtel settlement files imported and auto-matched on reference and amount
  • Unmatched items sit in an exceptions queue — never silently absorbed
  • Rider cash-up: collected · handed in · outstanding · variance, per rider per day
  • Variance requires a reason and supervisor sign-off
  • Daily USPS Enterprise Payment Account reconciliation against labels issued
  • Export to Excel, PDF and accounting-format CSV
Phase Nine

Auction Management new

Uncollected packages have a defined, time-boxed path out of the warehouse, with notice and settlement.

9.1

The Clock

Available for collection
Day 0
Collection window
Weeks 1–3
Auction notice
Week 4
Moves to Auction
End week 4
Sold & settled
StageTimingSystem behaviour
AvailableDay 0ready_for_collection_at stamped at break-out or release. Customer notified. Clock starts. Storage charges continue to accrue.
Collection windowWeeks 1–3Normal ageing notifications at 7 and 14 days. Package remains fully in the customer's account.
Auction noticeWeek 4Scheduled job flags the package auction_pending and fires notices on WhatsApp and email. Flagged in the admin auction queue.
Moves to auctionEnd week 4Status → auction. Package leaves the customer's account and live stock, and joins the auction list with photographs, category, weight and reserve price.
SettlementAfter saleProceeds recorded against the customer's original debt. Any balance is written off or refunded, and posts to the Financial Sheet.
Guards. All timings are configurable, and any package can be held back with a recorded reason. A package with an open dispute, an approved payment plan, or a failed delivery currently in re-attempt must not auto-progress — check these before the scheduled job flags anything. The clock also restarts when a package returns to the warehouse after exhausted delivery attempts.
Phase Ten

Activity Log, Tracking Page & Public API

Everything that happened, everything the customer can see, and everything a partner can pull.

10.1

Full Activity Log new

Coverage

  • Every action by every user across the whole platform — logins, price changes, discounts, payments, refunds, releases, overrides, verifications, assignments, deletions, incognito sessions, auction decisions
  • Written by a central hook, not by each controller — a module that forgets to log is the failure mode

Record shape

  • user_id, role, action, entity type and id, before_json, after_json, timestamp, IP
  • Searchable and filterable by user, role, action type, entity and date range
  • Append-only. No UPDATE or DELETE grant on the table for the application user
  • Exportable for audit
10.2

Tracking Page new

Content

  • One box resolves RFC, RAP, RSP, RCP, USPS and consolidation references
  • Complete event history — date, time, location, and the action behind each event
  • Package detail — weight, dimensions, category, contents, declared value
  • Photographs from intake, repack and delivery on the timeline
  • Video where captured — arrival condition, damage evidence, disputed handover
  • Map view; consolidation view; delivery window and rider details once assigned

Access

  • Basic detail public; full detail after login
  • Price breakdown, payment status and documents render only for the authenticated owner — the public view must not leak commercial data on a guessed reference
  • Media served through signed, expiring URLs
  • Share button produces a link and a pre-filled WhatsApp message
10.3

Public API for External Developers new

AreaEndpointsScope
TrackingGET /track/{reference}Public — no key
Rates & quotesPOST /quotes/courier · /air · /sea · /item · /usps-labelread:rates
PackagesGET /packages · GET /packages/{ref}read:packages
Invoices & paymentsGET /invoices · GET /paymentsread:invoices
DeliveriesGET /deliveries/{id}read:deliveries
Webhookspackage.status.changed · payment.received · label.issued · delivery.completedPer registered endpoint
  • Developers self-register and generate keys; each key scoped and optionally IP allow-listed. Raptors sees every developer, key and usage figure, and can revoke instantly. Test environment with sample data. The same API powers the Raptors mobile application.
One business layer. The portal, the admin, the rider page, the mobile app and external developers all read through this API. Business logic lives on the server and is never duplicated in a client — which is why a price rendered by a partner integration is always the price Raptors would charge.
Reference

Notification Dispatcher & Bulk Messaging

One dispatcher, four channels. Fires on every status change and every account activity.

ChannelProviderUsed for
WhatsAppWhatsApp Business APIPrimary channel — every status change and every activity, with PDF, image and video attachments
SMSHubtel SMS with Ghanaian sender ID; mNotify or Hubtel SMS as fallbackOTPs, delivery PINs, out-for-delivery alerts, payment confirmations
EmailSMTPInvoices, receipts, PODs, verification outcomes, auction notices, password flows
In-appNotification centrePersistent searchable history with deep links and unread count

Bulk messaging with media new

  • Campaigns carry images — flyers, rate cards, notices
  • Campaigns carry video — announcements, how-to clips, promotions
  • Audience built by filtering on customer status, debt position, package status, category, zone or agency
  • Preview before send; scheduled send; throttled to provider limits
  • Campaign report: sent · delivered · read · failed · opted out

Dispatcher behaviour

  • Queue-based with retry — three attempts, exponential back-off
  • Persistent failure moves to a dead-letter queue and raises an alert
  • Quiet hours — no SMS or WhatsApp 22:00–06:00 unless flagged urgent
  • Per-customer channel preferences, opt-in and opt-out
  • Message log with channel, status, timestamp and unit cost; cost posts to the Financial Sheet

Build sequence — 8 weeks

WeekBuildVerifiable outcome
1Requirements, designs, server setup; USPS and Hubtel applications lodgedPlan and designs signed off
2Accounts, Ghana Card verification, RFC/RAP/RSP/RCP references, roles, activity log, settingsA customer registers, is verified, and receives an RFC
3Pricing engine — items, weight, customs, categories, multicurrency, discounts, calculatorAny item prices in GHS and USD
4Paystack, Hubtel, bank transfer, split payments, invoicing, Financial Sheet, debtA customer pays in parts; the money and debt are visible
5USPS pricing, labels in all three sizes, printing, tracking; warehouse intake, Scan and PriceA label issues; an unpriceable package is priced at intake
6Air, sea and courier packages, consolidation, category export, tracking page with mediaFull package lifecycle end to end
7Last mile, rider pricing and commission, Riders page, POD, auctions, dashboards, incognito, APIA delivery completes with commission booked; an external key returns data
8Bulk messaging with media, testing, corrections, data loading, training, go-liveLive and in use