Back to all work
Case Study · 01 Membership · Matchmaking · Affiliate Launched May 1, 2026

Kisses & Huggs Club

A faith-based community platform — built from the ground up. Forty-four custom plugins, twenty-five database tables, two currencies, three referral streams, one developer.

Stack
WordPress 6.xAstra · Hostinger Cloud · PHP 8.1
Role
Solo buildArchitecture · code · launch
Status
LivePaying signups in NGN & USD
The brief

A community platform that off-the-shelf couldn't reach.

Kisses & Huggs Club exists at an awkward intersection for the WordPress ecosystem. It's a paid membership site, yes — but also a couples-only feature space gated by spousal linkage, a matchmaking surface for unmarried members, a counseling-booking engine, and a three-stream affiliate programme that pays referrers for life. Every off-the-shelf membership plugin handles one of those well. None handle all of them. So none of them were used.

The platform was built from scratch as a constellation of 44 custom WordPress plugins, sharing roughly 25 database tables under a unified wp_khc_* namespace. Authentication, payment handling, member experience, content rotation, referral commissions, couple gating — every line of it bespoke. Hosted on Hostinger Cloud, layered over the Astra theme, no membership boilerplate, no MemberPress, no Paid Memberships Pro.

The platform launched live and accepting paid signups on Friday, May 1, 2026 at 10:00 AM West Africa Time, in two currencies (NGN and USD), with full email deliverability stack verified end-to-end (SPF + DKIM + DMARC) before the first member signed up.

Architecture

Forty-four plugins, one shared namespace.

The plugin count looks alarming until you understand the design principle: every feature is its own plugin. This isn't bloat — it's separation of concerns enforced at the file system. Onboarding logic lives in khc-signup. Subscription renewal logic in khc-subscriptions. Referral commissions in khc-referrals. Pastoral counseling bookings in khc-court. The couples-only feature space in khc-intimacy-chamber. Each plugin is independently activatable, deactivatable, and debuggable. None depends on a third-party library beyond what WordPress already ships.

What ties them together is shared data. Every plugin reads from and writes to the same wp_khc_* tables, and a small library of helper functions (khc_get_user_status(), khc_fx_usd_to_ngn(), khc_chamber_get_couple_id()) act as the seams. Schema versioning lives in wp_options flags — every plugin checks its schema version on admin page load and runs missing migrations before continuing. Idempotent. Deployable mid-day. Zero downtime required.

The eleven plugins that carry the platform

/01
khc-signup
Multi-stage onboarding · Path A/B currency detection
/02
khc-subscriptions
Annual renewal · Strategy C extension logic
/03
khc-referrals
Three-stream commissions · composite UNIQUE indexing
/04
khc-court
Pastoral booking · idempotent commission payouts
/05
khc-intimacy-chamber
Couples-only space · daily content rotation
/06
khc-fx
USD↔NGN single source of truth · currently 1373
/07
khc-hub-matchmaking
Eligibility gates · profile system
/08
khc-classroom
Internal LMS · marriage growth modules
/09
khc-paystack
Cross-cutting payment integration
/10
khc-daily-cron
Scheduled rotation · email dispatch · expiry checks
/11
… plus 33 more
Wallets, prayer wall, revelations, journaling, etc.
Membership model

A single tier, by pastoral conviction.

Most modern membership platforms tier aggressively — Bronze, Silver, Gold; or Free, Pro, Enterprise. KHC went the opposite way. Pay one fee, get full access. No content gating between members. The pastoral conviction was that gatekeeping spiritual growth behind tiers contradicts the platform's mission.

What made this technically interesting wasn't the simplicity of the tier model — it was the dual-currency routing built around it. Nigerian members pay ₦10,000/year. International members pay $10/year. Founder accounts (the early test community) were grandfathered with lifetime access. Three states; one boolean check.

Path Currency Annual fee Stored as
Path A · Nigerian members NGN ₦10,000 subscription_expires_at
Path B · International members USD $10 (~₦13,730) subscription_expires_at
Founders · grandfathered Lifetime is_lifetime = 1

Access is a single check, used everywhere across the platform: is_lifetime = 1 OR subscription_expires_at > NOW(). One predicate, no edge cases. Renewals extend from MAX(today, current_expiry) + 365 days — so members who renew early don't lose unused time, and members who renew after lapsing start fresh from today.

The standout

A three-stream referral system that pays for life.

This is the most architecturally interesting piece of the platform. Most affiliate systems pay once, on signup, and never again. KHC pays three different ways from a single referral relationship — and continues paying as long as the invitee remains a member.

Stream Trigger Payout
Stream 1 · Signup Invitee completes paid signup Flat ₦2,000 to inviter wallet
Stream 2 · Yearly Invitee renews their annual subscription Flat ₦2,000 every year, in perpetuity
Stream 3 · Court Invitee books any pastoral counseling session 10% of NGN paid, every session, forever

The implementation challenge sat in Stream 2 and Stream 3 — both required rigorous idempotency to prevent double-payouts. A naive implementation would pay every time a renewal job runs. With WP-Cron firing potentially multiple times across overlapping requests, that meant the database needed to be the guard, not the application code.

Stream 2: composite UNIQUE keys at the schema layer

The challenge: ensure exactly one ₦2,000 payment per invitee per calendar year — even if the renewal job runs twice, even under a race condition. The solution sat one level below the application:

Schema migration · v1 SQL
ALTER TABLE wp_khc_referrals
  ADD COLUMN referral_year SMALLINT(5) UNSIGNED NOT NULL DEFAULT 0,
  DROP INDEX invitee_user_id,
  ADD UNIQUE KEY uniq_invitee_year (invitee_user_id, referral_year);

With this composite UNIQUE in place, any second insert for the same (invitee, year) pair fails at the database level. No race conditions. No double payouts. The application code can attempt the insert naively without locking — the database rejects duplicates atomically.

The application code can be naive. The database is the guard. — design principle, KHC referrals engine

Stream 3: notes-field idempotency without schema changes

For the Court 10% commission stream, a different idempotency challenge: one Court booking should produce exactly one 10% payout, ever. But adding a separate ledger table felt heavy for a smaller volume of events. Instead, the existing notes column on wp_khc_transactions — already used for human auditing — was doubled as the deduplication key:

khc-court · commission payout SQL
-- Before paying, check if this session has already paid out
SELECT 1 FROM wp_khc_transactions
 WHERE notes LIKE 'court_session_id:42%';

-- If empty, write the payout with the session ID embedded
INSERT INTO wp_khc_transactions ...
  notes = CONCAT('court_session_id:', $session_id, '|paid:', NOW());

Cheap, correct, no migration required. The notes column was already there for auditing. Doubling it as the dedup key meant zero schema friction and full traceability — every payout self-documents which session triggered it.

Payments

Two currencies, one rate, no hardcodes.

Members from Nigeria pay in NGN. International members see USD pricing. Paystack accepts both, but actual charges happen in NGN regardless of what the member sees. The system tracks both surfaces independently:

  • $usd_displayed — what the member saw at checkout (used for receipts and transparency)
  • $ngn_to_charge — what Paystack actually transacted
  • khc_fx_usd_to_ngn() — the single source of truth for the rate, currently 1373

Every place in the codebase that converts currency calls the helper. Nowhere is the rate hardcoded. Changing one wp_options row updates pricing everywhere — signup, renewal, Court booking, member dashboard — atomically. When the rate changes, it changes once.

One-time charges, by pastoral choice

Paystack supports auto-renew subscriptions natively. KHC deliberately doesn't use them. Members re-commit each year intentionally rather than have a card auto-charged silently. The renewal flow nudges members at 30, 14, and 7 days before expiry — but never auto-charges. Conversion is slightly lower than auto-renew would be; pastoral integrity is higher. A deliberate trade-off.

Couple gating

The Intimacy Chamber: both spouses, same content, same day.

The Chamber is the most content-heavy module on the platform — and the most security-sensitive. Both spouses must be paid members and linked as a couple to enter. Once inside, they share six daily-rotating content surfaces:

Content type Active items Cycle
Conversation cards80Daily, deterministic
Date ideas60Random, on demand
Sex positions (married-only)50Daily, deterministic
Reflection questions150Daily
Tips150Daily
Challenges52Weekly (one full year)

Deterministic daily rotation

Both spouses see the same content on the same day. The rotation index is computed from days-since-epoch modulo content count — no random seeds, no per-user variation. This was a pastoral decision before it was a technical one: prompts that couples are meant to discuss together must match.

Defensive permission checks at every read and write

Every AJAX handler in the Chamber verifies three things before doing any work: a valid nonce, a logged-in user, and Chamber access (gated by the khc_chamber_get_couple_id() helper). Then — and this is the part most implementations skip — it re-verifies that the couple_id derived from the session user matches the couple_id of any record being mutated. This prevents URL-tampering attacks where User A might guess record IDs and try to delete or modify User B's data. Defense in depth, applied consistently.

Other decisions worth noting

Small choices that compound.

Email queue, never synchronous

Rather than send emails inline on member action — which would make every signup, every renewal, every Court booking wait on SMTP latency — every notifiable event creates a row in wp_khc_notifications. A WP-Cron job dispatches up to N emails per minute via Hostinger SMTP. Members on slow Nigerian mobile networks never wait for an email to send.

Suspended, never deleted

Pre-launch, 23 test accounts existed. Deleting them risked cascade integrity issues across five-plus tables (wallets, transactions, referrals, profiles, notifications). Instead they were marked status = 'suspended', hub_eligible = 0. Real members never see them in the activity feed or matchmaking surface; the data is preserved for audit.

The community wall filters at the query level

The community wall (/wall) shows every member's public activity. To prevent demo accounts from polluting the feed, every feed query JOINs to wp_khc_profiles and filters where p.status = 'active'. Suspended accounts disappear instantly from public surfaces without any data being deleted.

AJAX-driven UX

Save reflection, set mood, add memory, draw conversation card, roll date idea, mark goal achieved, add win — every interaction inside the platform that doesn't require a page change is AJAX, with optimistic UI updates and server-side validation. Members feel instant feedback even on slow connections.

Pre-launch verification

Twenty-six SQL phases, one green light.

Before the platform opened to paid signups, every database invariant was verified across 26 SQL verification phases. This level of rigor is unusual for solo-developer projects — but with paid recurring billing, currency routing, and lifetime referral attribution all going live simultaneously, anything less would have been negligence.

36
Settings rows
verified
542
Active Chamber
content items
44
Plugins active
without errors
20
WP-Cron jobs
scheduled live

What was verified at the moment of launch: schema migrations applied, composite UNIQUE keys present, all 11 critical pages live with correct templates, Paystack live keys configured for both currencies, FX rate set to 1373, no dangling profiles, no dangling wallets, no null-amount transactions. Email deliverability stack checked end-to-end: SPF, three DKIM selectors, DMARC.

The outcome

Live. Friday, 1 May 2026, 10am WAT.

The platform launched with zero downtime, full Paystack live integration in two currencies, 542 daily-rotating content items pre-loaded across six rotation surfaces, and an end-to-end-verified email deliverability stack — before the first member signed up. Real signups began immediately at user ID 27 (the founder accounts, designated test accounts, and quarantined demo accounts occupied IDs 1–26).

The companion site kissesandhuggs.org — a Divi-based devotional site — drives traffic to the new platform via a sticky top bar, footer CTA, and homepage hero blocks.

What's next

Roadmap items in active development include expanding the sex positions library from 50 to 100+ for longer rotation cycles, an admin moderation UI for the community wall (currently SQL-based), email deliverability tuning for OTP body copy, and Meta Ads campaigns targeted at Nigerian Christians aged 22–45.

Next case study · 02

A multi-tenant church finance SaaS, built solo in five months. Multi-tenancy in the WHERE clause, multi-currency accounting, recurring Paystack billing — live with paying customers.