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.
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.
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.
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.
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.
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:
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.
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:
-- 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.
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 transactedkhc_fx_usd_to_ngn() — the single source of truth for the rate, currently 1373Every 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.
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.
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 cards | 80 | Daily, deterministic |
| Date ideas | 60 | Random, on demand |
| Sex positions (married-only) | 50 | Daily, deterministic |
| Reflection questions | 150 | Daily |
| Tips | 150 | Daily |
| Challenges | 52 | Weekly (one full year) |
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.
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.
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.
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 (/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.
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.
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.
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 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.
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.