✓ Copied to clipboard

HRIH Investments — Backend Setup & Deployment

Complete Supabase schema · API integrations · Environment config · Go-live checklist

Supabase PayFast · Stripe SendGrid · Twilio Google Drive API WhatsApp Business
Platform Blueprint

System Architecture

The HRIH Investments platform uses a Jamstack architecture — static HTML/JS frontend files hosted on Vercel or Netlify, with Supabase as the backend-as-a-service (database, auth, storage, real-time). All integrations run through Supabase Edge Functions (serverless), keeping your API keys secure on the server side.

ARCHITECTURE
┌─────────────────────────────────────────────────────────┐
│                   HRIH PLATFORM STACK                    │
├─────────────────────────────────────────────────────────┤
│                                                          │
│  FRONTEND (hrih-membership-platform.html)               │
│  ├── Public Site        → Netlify / Vercel CDN          │
│  ├── Member Dashboard   → Auth-gated                    │
│  └── Back-Office CRM    → Admin-only                    │
│                                                          │
│  BACKEND (Supabase)                                      │
│  ├── PostgreSQL DB      → All data                      │
│  ├── Auth               → JWT · Google OAuth            │
│  ├── Storage            → Products, avatars, invoices   │
│  ├── Realtime           → Live CRM updates              │
│  └── Edge Functions     → API calls (PayFast, WA, etc) │
│                                                          │
│  INTEGRATIONS                                            │
│  ├── PayFast            → ZAR payments (SA)             │
│  ├── Stripe             → Intl card payments            │
│  ├── SendGrid           → Email from your domain        │
│  ├── Twilio/360dialog   → WhatsApp Business API         │
│  ├── Google Drive API   → Product file sync             │
│  ├── Google Business    → Profile & reviews             │
│  ├── ScraperAPI         → Product web scraping          │
│  └── Meta / LinkedIn    → Social posting                │
└─────────────────────────────────────────────────────────┘
What You Need

Services & Monthly Costs

🗄
Supabase
Database, auth, storage, real-time, edge functions. Free tier is extremely generous — supports up to 50,000 MAUs.
Free → $25/mo (Pro)
🌐
Vercel / Netlify
Host the frontend HTML files with global CDN, custom domain, and HTTPS. Auto-deploy from GitHub.
Free → $20/mo
💳
PayFast
SA payment gateway. EFT, card, SnapScan, Ozow. No monthly fee — pay 3.5% + R2.00 per transaction.
Free + 3.5% per txn
💳
Stripe
International card processing. Required if you want to accept USD/EUR. 2.9% + $0.30 per transaction.
Free + 2.9% per txn
📧
Zoho Mail + SendGrid
Zoho gives you @hrihinvestments.co.za email. SendGrid sends bulk/transactional at 100/day free, 40,000/day on free trial.
Free → $15/mo
📱
Twilio / 360dialog
WhatsApp Business API. 360dialog is cheaper for SA: ~€49/mo flat. Twilio is pay-per-message at ~$0.005/msg.
~R900/mo flat
📂
Google Drive API
Free for product file sync. Requires a Google Cloud project and OAuth consent screen — 15 minute setup.
Free
🤖
ScraperAPI
1,000 API credits/month free. Sufficient for regular product scraping. Upgrade at $49/mo for 250,000 credits.
Free → $49/mo
🔑
Domain Email
Register hrihinvestments.co.za (if not done). ~R150/yr via Afrihost, Domains.co.za or xneelo.
~R150/year
Total minimum cost to go live: R0 to start (Supabase + Vercel + PayFast free tiers cover everything). Once you hit scale, budget approximately R1,500–R2,500/month for the full production stack.
Database Design

Supabase Database Schema

Run this SQL in your Supabase SQL Editor (Dashboard → SQL Editor → New Query). It creates all 12 tables with relationships, indexes, and triggers.

SQL
-- ══════════════════════════════════════════════════════
-- HRIH INVESTMENTS — SUPABASE DATABASE SCHEMA v1.0
-- Run in: Supabase Dashboard → SQL Editor
-- ══════════════════════════════════════════════════════

-- Enable UUID extension
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";

-- ── PROFILES (extends Supabase auth.users) ──────────
CREATE TABLE profiles (
  id           UUID PRIMARY KEY REFERENCES auth.users(id) ON DELETE CASCADE,
  full_name    TEXT,
  phone        TEXT,
  company_name TEXT,
  avatar_url   TEXT,
  role         TEXT DEFAULT 'member',  -- 'member' | 'admin'
  created_at   TIMESTAMPTZ DEFAULT NOW(),
  updated_at   TIMESTAMPTZ DEFAULT NOW()
);

-- ── MEMBERSHIP TIERS ─────────────────────────────────
CREATE TABLE tiers (
  id           UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
  name         TEXT NOT NULL,              -- 'foundation' | 'strategic' | 'corporate'
  display_name TEXT NOT NULL,
  price_monthly NUMERIC(10,2) NOT NULL,
  price_annual  NUMERIC(10,2),
  features     JSONB,                     -- Array of feature strings
  max_sessions  INT DEFAULT 0,
  is_active    BOOLEAN DEFAULT TRUE,
  sort_order   INT DEFAULT 0
);

-- Seed tier data
INSERT INTO tiers (name, display_name, price_monthly, price_annual, max_sessions, sort_order)
VALUES
  ('foundation', 'Foundation', 499.00, 4990.00, 0, 1),
  ('strategic', 'Strategic', 1499.00, 14990.00, 2, 2),
  ('corporate', 'Corporate', 3999.00, 39990.00, -1, 3);  -- -1 = unlimited

-- ── MEMBERSHIPS ──────────────────────────────────────
CREATE TABLE memberships (
  id              UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
  user_id         UUID NOT NULL REFERENCES profiles(id) ON DELETE CASCADE,
  tier_id         UUID NOT NULL REFERENCES tiers(id),
  status          TEXT DEFAULT 'active',  -- 'trial'|'active'|'paused'|'cancelled'
  billing_cycle   TEXT DEFAULT 'monthly', -- 'monthly' | 'annual'
  start_date      DATE NOT NULL DEFAULT CURRENT_DATE,
  next_billing    DATE,
  trial_ends      DATE,
  payfast_token   TEXT,                   -- PayFast recurring token
  stripe_sub_id   TEXT,                   -- Stripe subscription ID
  sessions_used   INT DEFAULT 0,
  created_at      TIMESTAMPTZ DEFAULT NOW()
);

-- ── PRODUCTS ─────────────────────────────────────────
CREATE TABLE products (
  id            UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
  name          TEXT NOT NULL,
  slug          TEXT UNIQUE NOT NULL,
  description   TEXT,
  category      TEXT NOT NULL,  -- 'webinar'|'ebook'|'video'|'software'|'consulting'|'template'
  price         NUMERIC(10,2) NOT NULL DEFAULT 0,
  price_type    TEXT DEFAULT 'one_time',  -- 'one_time' | 'subscription'
  file_url      TEXT,              -- Supabase Storage URL
  drive_url     TEXT,              -- Google Drive URL
  thumbnail     TEXT,
  access_tier   TEXT DEFAULT 'public',  -- 'public'|'foundation'|'strategic'|'corporate'
  is_active     BOOLEAN DEFAULT TRUE,
  is_featured   BOOLEAN DEFAULT FALSE,
  source        TEXT DEFAULT 'manual',   -- 'manual'|'scraped'|'drive'|'upload'
  metadata      JSONB,
  sales_count   INT DEFAULT 0,
  created_at    TIMESTAMPTZ DEFAULT NOW(),
  updated_at    TIMESTAMPTZ DEFAULT NOW()
);

-- ── ORDERS ───────────────────────────────────────────
CREATE TABLE orders (
  id            UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
  user_id       UUID REFERENCES profiles(id),
  product_id    UUID REFERENCES products(id),
  amount        NUMERIC(10,2) NOT NULL,
  currency      TEXT DEFAULT 'ZAR',
  status        TEXT DEFAULT 'pending',  -- 'pending'|'paid'|'failed'|'refunded'
  payment_method TEXT,                  -- 'payfast'|'stripe'|'eft'
  payment_ref   TEXT,
  download_count INT DEFAULT 0,
  created_at    TIMESTAMPTZ DEFAULT NOW()
);

-- ── INVOICES ─────────────────────────────────────────
CREATE TABLE invoices (
  id            UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
  invoice_num   TEXT UNIQUE NOT NULL,    -- 'INV-0001'
  user_id       UUID REFERENCES profiles(id),
  client_name   TEXT,
  client_email  TEXT,
  items         JSONB NOT NULL,          -- [{desc, qty, unit_price, total}]
  subtotal      NUMERIC(10,2),
  vat_amount    NUMERIC(10,2),
  total         NUMERIC(10,2) NOT NULL,
  status        TEXT DEFAULT 'draft',   -- 'draft'|'sent'|'paid'|'overdue'
  due_date      DATE,
  paid_at       TIMESTAMPTZ,
  notes         TEXT,
  pdf_url       TEXT,                   -- Generated PDF in Storage
  created_at    TIMESTAMPTZ DEFAULT NOW(),
  updated_at    TIMESTAMPTZ DEFAULT NOW()
);

-- ── QUOTES ───────────────────────────────────────────
CREATE TABLE quotes (
  id            UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
  quote_num     TEXT UNIQUE NOT NULL,    -- 'QT-0001'
  client_name   TEXT NOT NULL,
  client_email  TEXT,
  items         JSONB NOT NULL,
  total         NUMERIC(10,2) NOT NULL,
  valid_until   DATE,
  status        TEXT DEFAULT 'draft',   -- 'draft'|'sent'|'accepted'|'declined'|'expired'
  notes         TEXT,
  converted_invoice UUID REFERENCES invoices(id),
  created_at    TIMESTAMPTZ DEFAULT NOW()
);

-- ── SESSIONS (consulting bookings) ───────────────────
CREATE TABLE sessions (
  id            UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
  user_id       UUID REFERENCES profiles(id),
  session_type  TEXT,               -- 'strategy'|'finance'|'compliance'|'general'
  scheduled_at  TIMESTAMPTZ,
  duration_min  INT DEFAULT 60,
  channel       TEXT DEFAULT 'zoom',  -- 'zoom'|'whatsapp'|'phone'|'meet'
  agenda        TEXT,
  notes         TEXT,
  status        TEXT DEFAULT 'requested',
  meeting_link  TEXT,
  created_at    TIMESTAMPTZ DEFAULT NOW()
);

-- ── LEADS ────────────────────────────────────────────
CREATE TABLE leads (
  id            UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
  name          TEXT NOT NULL,
  email         TEXT,
  phone         TEXT,
  company       TEXT,
  interest_tier TEXT,
  stage         TEXT DEFAULT 'new',  -- 'new'|'contacted'|'quoted'|'converted'|'lost'
  source        TEXT,               -- 'whatsapp'|'website'|'referral'|'social'
  notes         TEXT,
  referred_by   UUID REFERENCES profiles(id),
  created_at    TIMESTAMPTZ DEFAULT NOW()
);

-- ── ENROLLMENTS (courses/products) ───────────────────
CREATE TABLE enrollments (
  id            UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
  user_id       UUID REFERENCES profiles(id),
  product_id    UUID REFERENCES products(id),
  progress_pct  INT DEFAULT 0,         -- 0-100
  last_module   INT DEFAULT 0,
  completed_at  TIMESTAMPTZ,
  created_at    TIMESTAMPTZ DEFAULT NOW(),
  UNIQUE(user_id, product_id)
);

-- ── NOTIFICATIONS ─────────────────────────────────────
CREATE TABLE notifications (
  id            UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
  user_id       UUID REFERENCES profiles(id),
  title         TEXT NOT NULL,
  body          TEXT,
  type          TEXT,               -- 'payment'|'session'|'product'|'system'
  is_read       BOOLEAN DEFAULT FALSE,
  action_url    TEXT,
  created_at    TIMESTAMPTZ DEFAULT NOW()
);

-- ── INDEXES ──────────────────────────────────────────
CREATE INDEX idx_memberships_user ON memberships(user_id);
CREATE INDEX idx_memberships_status ON memberships(status);
CREATE INDEX idx_orders_user ON orders(user_id);
CREATE INDEX idx_orders_status ON orders(status);
CREATE INDEX idx_invoices_status ON invoices(status);
CREATE INDEX idx_products_category ON products(category);
CREATE INDEX idx_notifications_user ON notifications(user_id, is_read);

-- ── AUTO-UPDATE TIMESTAMPS ───────────────────────────
CREATE OR REPLACE FUNCTION update_updated_at()
RETURNS TRIGGER AS $$
BEGIN
  NEW.updated_at = NOW();
  RETURN NEW;
END;
$$ LANGUAGE plpgsql;

CREATE TRIGGER trg_profiles_updated
  BEFORE UPDATE ON profiles
  FOR EACH ROW EXECUTE FUNCTION update_updated_at();

CREATE TRIGGER trg_products_updated
  BEFORE UPDATE ON products
  FOR EACH ROW EXECUTE FUNCTION update_updated_at();

CREATE TRIGGER trg_invoices_updated
  BEFORE UPDATE ON invoices
  FOR EACH ROW EXECUTE FUNCTION update_updated_at();

-- ── AUTO-CREATE PROFILE ON SIGNUP ────────────────────
CREATE OR REPLACE FUNCTION handle_new_user()
RETURNS TRIGGER AS $$
BEGIN
  INSERT INTO profiles (id, full_name)
  VALUES (NEW.id, NEW.raw_user_meta_data->>>'full_name');
  RETURN NEW;
END;
$$ LANGUAGE plpgsql SECURITY DEFINER;

CREATE TRIGGER on_auth_user_created
  AFTER INSERT ON auth.users
  FOR EACH ROW EXECUTE FUNCTION handle_new_user();

-- ── INVOICE SEQUENCE FUNCTION ─────────────────────────
CREATE SEQUENCE invoice_seq START WITH 43;  -- Starts at INV-0043

CREATE OR REPLACE FUNCTION next_invoice_num()
RETURNS TEXT AS $$
  SELECT 'INV-' || LPAD(nextval('invoice_seq')::TEXT, 4, '0');
$$ LANGUAGE sql;

CREATE SEQUENCE quote_seq START WITH 19;

CREATE OR REPLACE FUNCTION next_quote_num()
RETURNS TEXT AS $$
  SELECT 'QT-' || LPAD(nextval('quote_seq')::TEXT, 4, '0');
$$ LANGUAGE sql;
Security

Row Level Security (RLS)

RLS ensures members can only see their own data. Admins (you) can see everything. Run this after the schema above.

SQL
-- ── ENABLE RLS ON ALL USER TABLES ─────────────────────
ALTER TABLE profiles      ENABLE ROW LEVEL SECURITY;
ALTER TABLE memberships   ENABLE ROW LEVEL SECURITY;
ALTER TABLE orders        ENABLE ROW LEVEL SECURITY;
ALTER TABLE invoices      ENABLE ROW LEVEL SECURITY;
ALTER TABLE sessions      ENABLE ROW LEVEL SECURITY;
ALTER TABLE enrollments   ENABLE ROW LEVEL SECURITY;
ALTER TABLE notifications ENABLE ROW LEVEL SECURITY;

-- ── HELPER: is_admin() ────────────────────────────────
CREATE OR REPLACE FUNCTION is_admin()
RETURNS BOOLEAN AS $$
  SELECT role = 'admin' FROM profiles WHERE id = auth.uid();
$$ LANGUAGE sql SECURITY DEFINER;

-- ── PROFILES ──────────────────────────────────────────
CREATE POLICY "Own profile" ON profiles
  FOR ALL USING (id = auth.uid() OR is_admin());

-- ── MEMBERSHIPS ───────────────────────────────────────
CREATE POLICY "Own membership" ON memberships
  FOR SELECT USING (user_id = auth.uid() OR is_admin());
CREATE POLICY "Admin manage memberships" ON memberships
  FOR ALL USING (is_admin());

-- ── ORDERS ────────────────────────────────────────────
CREATE POLICY "Own orders" ON orders
  FOR SELECT USING (user_id = auth.uid() OR is_admin());

-- ── INVOICES ──────────────────────────────────────────
CREATE POLICY "Own invoices" ON invoices
  FOR SELECT USING (user_id = auth.uid() OR is_admin());
CREATE POLICY "Admin manage invoices" ON invoices
  FOR ALL USING (is_admin());

-- ── PRODUCTS (public read, admin write) ───────────────
CREATE POLICY "Public products" ON products
  FOR SELECT USING (is_active = TRUE);
CREATE POLICY "Admin manage products" ON products
  FOR ALL USING (is_admin());

-- ── NOTIFICATIONS ─────────────────────────────────────
CREATE POLICY "Own notifications" ON notifications
  FOR ALL USING (user_id = auth.uid() OR is_admin());
Set yourself as admin: After creating your account, run: UPDATE profiles SET role = 'admin' WHERE id = 'YOUR-USER-UUID'; Replace YOUR-USER-UUID with your actual Supabase user ID from Auth → Users.
Serverless

Supabase Edge Functions

Edge Functions run on Deno at the edge — they keep your API keys secret and handle webhooks from PayFast, Stripe, and WhatsApp. Deploy with the Supabase CLI.

PayFast Webhook Handler

TYPESCRIPT / DENO
// supabase/functions/payfast-webhook/index.ts
// Handles PayFast ITN (Instant Transaction Notification)
// Deploy: supabase functions deploy payfast-webhook

import { serve } from "https://deno.land/std@0.168.0/http/server.ts"
import { createClient } from "https://esm.sh/@supabase/supabase-js@2"

serve(async (req) => {
  const body = await req.formData()
  const data = Object.fromEntries(body)

  // Verify PayFast signature
  const pfData = { ...data }
  delete pfData.signature
  const pfString = Object.entries(pfData)
    .map(([k, v]) => `${k}=${encodeURIComponent(String(v))}`)
    .join('&')

  const supabase = createClient(
    Deno.env.get('SUPABASE_URL')!,
    Deno.env.get('SUPABASE_SERVICE_ROLE_KEY')!
  )

  if (data.payment_status === 'COMPLETE') {
    // Update order status
    await supabase
      .from('orders')
      .update({ status: 'paid', payment_ref: data.pf_payment_id })
      .eq('id', data.m_payment_id)

    // Activate membership if this was a subscription signup
    if (data.token) {
      await supabase
        .from('memberships')
        .update({ status: 'active', payfast_token: data.token })
        .eq('id', data.m_payment_id)
    }

    // Create notification
    await supabase.from('notifications').insert({
      user_id: data.custom_str1,
      title: 'Payment received',
      body: `R${data.amount_gross} confirmed via PayFast`,
      type: 'payment'
    })
  }

  return new Response('OK', { status: 200 })
})

Send WhatsApp Message

TYPESCRIPT / DENO
// supabase/functions/send-whatsapp/index.ts
// Sends WhatsApp messages via 360dialog API

import { serve } from "https://deno.land/std@0.168.0/http/server.ts"

serve(async (req) => {
  const { to, message, template } = await req.json()
  const apiKey = Deno.env.get('DIALOG360_API_KEY')!

  const payload = template ? {
    // Template message (for first-contact / automated)
    messaging_product: "whatsapp",
    to,
    type: "template",
    template: { name: template.name, language: { code: "en" }, components: template.components }
  } : {
    // Free-form message (within 24hr window)
    messaging_product: "whatsapp",
    to,
    type: "text",
    text: { body: message }
  }

  const res = await fetch(
    'https://waba.360dialog.io/v1/messages',
    {
      method: 'POST',
      headers: { 'D360-API-KEY': apiKey, 'Content-Type': 'application/json' },
      body: JSON.stringify(payload)
    }
  )

  const result = await res.json()
  return new Response(JSON.stringify(result), {
    headers: { 'Content-Type': 'application/json' }
  })
})

Send Domain Email (SendGrid)

TYPESCRIPT / DENO
// supabase/functions/send-email/index.ts

import { serve } from "https://deno.land/std@0.168.0/http/server.ts"

serve(async (req) => {
  const { to, subject, html, text } = await req.json()

  const res = await fetch('https://api.sendgrid.com/v3/mail/send', {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${Deno.env.get('SENDGRID_API_KEY')}`,
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      personalizations: [{ to: [{ email: to }] }],
      from: {
        email: 'hilton@hrihinvestments.co.za',
        name: 'Hilton Hartnick — HRIH Investments'
      },
      subject,
      content: [
        { type: 'text/plain', value: text || subject },
        { type: 'text/html', value: html }
      ]
    })
  })

  return new Response(
    JSON.stringify({ success: res.ok, status: res.status }),
    { headers: { 'Content-Type': 'application/json' } }
  )
})

Google Drive Product Sync

TYPESCRIPT / DENO
// supabase/functions/sync-drive/index.ts
// Lists files from a shared Google Drive folder and upserts to products table

import { serve } from "https://deno.land/std@0.168.0/http/server.ts"
import { createClient } from "https://esm.sh/@supabase/supabase-js@2"

serve(async (req) => {
  const { folder_id } = await req.json()
  const apiKey = Deno.env.get('GOOGLE_API_KEY')!

  // List files in folder
  const driveRes = await fetch(
    `https://www.googleapis.com/drive/v3/files?q='${folder_id}'+in+parents&key=${apiKey}&fields=files(id,name,mimeType,size,webViewLink,thumbnailLink)`
  )
  const { files } = await driveRes.json()

  const supabase = createClient(
    Deno.env.get('SUPABASE_URL')!,
    Deno.env.get('SUPABASE_SERVICE_ROLE_KEY')!
  )

  // Map Drive files to products
  const products = files.map((f: any) => ({
    name: f.name.replace(/\.[^.]+$/, ''),  // Strip extension
    slug: f.name.toLowerCase().replace(/\s+/g, '-').replace(/[^a-z0-9-]/g, ''),
    category: guessCategory(f.mimeType, f.name),
    drive_url: f.webViewLink,
    thumbnail: f.thumbnailLink,
    source: 'drive',
    is_active: true
  }))

  const { data, error } = await supabase
    .from('products')
    .upsert(products, { onConflict: 'slug' })

  return new Response(
    JSON.stringify({ synced: products.length, error }),
    { headers: { 'Content-Type': 'application/json' } }
  )
})

function guessCategory(mime: string, name: string): string {
  if (mime.includes('video')) return 'video'
  if (mime.includes('pdf') || name.includes('ebook')) return 'ebook'
  if (mime.includes('spreadsheet')) return 'template'
  if (name.includes('software') || name.includes('.exe')) return 'software'
  return 'ebook'
}
Configuration

Environment Variables

Create a .env file for local development. For production, set these in Vercel/Netlify dashboard and Supabase Edge Function secrets.

.ENV
# ── SUPABASE ─────────────────────────────────────────
NEXT_PUBLIC_SUPABASE_URL=https://your-project.supabase.co
NEXT_PUBLIC_SUPABASE_ANON_KEY=eyJhbGci...your-anon-key
SUPABASE_SERVICE_ROLE_KEY=eyJhbGci...your-service-role-key

# ── PAYFAST (ZAR Payments) ────────────────────────────
PAYFAST_MERCHANT_ID=your-merchant-id
PAYFAST_MERCHANT_KEY=your-merchant-key
PAYFAST_PASSPHRASE=your-passphrase
PAYFAST_SANDBOX=false  # true for testing

# ── STRIPE (International) ────────────────────────────
STRIPE_SECRET_KEY=sk_live_...
STRIPE_PUBLISHABLE_KEY=pk_live_...
STRIPE_WEBHOOK_SECRET=whsec_...

# ── SENDGRID (Email) ──────────────────────────────────
SENDGRID_API_KEY=SG.your-api-key
FROM_EMAIL=hilton@hrihinvestments.co.za
FROM_NAME=Hilton Hartnick — HRIH Investments

# ── WHATSAPP (360dialog) ──────────────────────────────
DIALOG360_API_KEY=your-360dialog-api-key
WA_BUSINESS_NUMBER=27610776410

# ── GOOGLE ────────────────────────────────────────────
GOOGLE_API_KEY=AIza...your-google-api-key
GOOGLE_CLIENT_ID=your-client-id.apps.googleusercontent.com
GOOGLE_CLIENT_SECRET=your-client-secret

# ── SCRAPERAPI ────────────────────────────────────────
SCRAPERAPI_KEY=your-scraperapi-key

# ── APP CONFIG ───────────────────────────────────────
SITE_URL=https://members.hrihinvestments.co.za
COMPANY_NAME=HRIH Investments (Pty) Ltd
COMPANY_REG=2015/036586/07
COMPANY_PHONE=+27 61 077 6410
COMPANY_ADDRESS=24 Siegelaar Street, Railton, Swellendam, 6740
VAT_RATE=0.15

Set Edge Function Secrets

TERMINAL
# Install Supabase CLI
npm install -g supabase

# Login and link your project
supabase login
supabase link --project-ref your-project-ref

# Set secrets for edge functions
supabase secrets set SENDGRID_API_KEY=SG.xxx
supabase secrets set DIALOG360_API_KEY=xxx
supabase secrets set PAYFAST_MERCHANT_ID=xxx
supabase secrets set GOOGLE_API_KEY=AIza...
supabase secrets set SCRAPERAPI_KEY=xxx

# Deploy all edge functions
supabase functions deploy payfast-webhook
supabase functions deploy send-whatsapp
supabase functions deploy send-email
supabase functions deploy sync-drive
Go Live

Deployment Options

▲ Vercel
Free → $20/mo
Best choice. Auto-deploys from GitHub, custom domain, global CDN, HTTPS. Add env variables in dashboard. Deploy in 2 minutes.
Netlify
Free → $19/mo
Excellent alternative to Vercel. Drag-and-drop deploy supported — just upload your HTML file. No GitHub required.
xneelo / cPanel
~R99/mo
South African hosting for your custom domain. FTP your HTML files directly. Best if you already have SA web hosting.

Deploy to Vercel in 3 steps

  1. Push to GitHub
    Create a GitHub repository named hrih-platform. Add your HTML files. Commit and push.
  2. Connect to Vercel
    Go to vercel.com → New Project → Import from GitHub. Select hrih-platform. Framework: Other.
  3. Add Domain & ENV
    In Vercel dashboard → Settings → Environment Variables: add all your .env keys. Then Settings → Domains: add members.hrihinvestments.co.za. Update DNS at your registrar. Live in minutes.
Launch

Go-Live Checklist

Work through this list in order. Tick each item before moving to the next.

Phase 1 — Foundation (Day 1–2)

  • Create Supabase project at supabase.com/dashboard
  • Run schema SQL in Supabase SQL Editor
  • Run RLS SQL and set your user as admin
  • Enable Google OAuth in Supabase Auth → Providers
  • Create Supabase Storage buckets: products, avatars, invoices, uploads
  • Upload platform HTML files to GitHub
  • Deploy to Vercel. Test on temp domain (xxx.vercel.app)
  • Connect custom domain: members.hrihinvestments.co.za

Phase 2 — Payments (Day 2–3)

  • Register PayFast merchant account at payfast.co.za
  • Set PayFast return/cancel/notify URLs to your Supabase function
  • Deploy payfast-webhook edge function
  • Test PayFast sandbox payment end-to-end
  • Optional: Create Stripe account for international payments

Phase 3 — Communications (Day 3–4)

  • Set up Zoho Mail. Create hilton@hrihinvestments.co.za
  • Create SendGrid account. Verify domain hrihinvestments.co.za
  • Add SPF, DKIM, DMARC DNS records for email deliverability
  • Deploy send-email edge function. Send test email.
  • Apply for 360dialog WhatsApp API (3–5 day approval)
  • Deploy send-whatsapp function once approved

Phase 4 — Products & Integrations (Day 4–7)

  • Create Google Cloud project. Enable Drive API. Get API key.
  • Deploy sync-drive function. Test with a shared folder.
  • Register ScraperAPI (free). Add key to env.
  • Add first 5 products manually through Back-Office CRM
  • Upload at least one ebook or template to test downloads
  • Connect Google Business profile (Google Business API)
  • Connect Facebook Business Manager for Meta integration

Phase 5 — Soft Launch (Day 7–14)

  • Create your own admin account. Test full member signup flow.
  • Test: sign up → payment → access product → receive email → receive WhatsApp
  • Invite 3–5 beta members. Collect feedback.
  • Send first email campaign to your existing 500+ client list
  • Post launch announcement across WhatsApp, LinkedIn, Facebook, Instagram
  • Go live — announce at hrihinvestments.web.za
Revenue projection at launch: If 10% of your existing 500+ clients join at Foundation tier (R499) = R24,950 MRR. If 5% join Strategic (R1,499) = additional R37,475 MRR. Total conservative projection: R62,425/mo from existing relationships alone — before any new client acquisition.
Quick Reference

Integration Setup Links

🗄
Supabase
Free tier — Start here
Create project → Run schema SQL → Copy URL and anon key to your .env file.
→ supabase.com/dashboard
💳
PayFast
Free to register · 3.5% per txn
Register merchant account → Get Merchant ID + Key → Set notify_url to your payfast-webhook function URL.
→ payfast.co.za/registration
📧
SendGrid
Free 100 emails/day
Create account → Settings → Sender Authentication → Authenticate hrihinvestments.co.za → Get API key.
→ app.sendgrid.com
📱
360dialog (WhatsApp)
€49/mo flat rate
Register → Submit Business Manager ID → Wait 3–5 days for approval → Get API key → Deploy send-whatsapp function.
→ 360dialog.com
📂
Google Drive API
Free
console.cloud.google.com → New Project → Enable Drive API → Create API Key → Restrict to Drive API → Add to env.
→ console.cloud.google.com
🤖
ScraperAPI
Free 1,000 credits/mo
Register → Get API key → Add to env as SCRAPERAPI_KEY → Platform scraper uses: api.scraperapi.com?api_key=KEY&url=TARGET
→ scraperapi.com
🔍
Google Business Profile
Free
Business Profile API → OAuth 2.0 → Manage reviews, post updates, sync hours from your CRM dashboard.
→ developers.google.com/my-business
📧
Zoho Mail
Free for 5 users
zoho.com/mail → Add domain → Verify DNS (MX records) → Create hilton@hrihinvestments.co.za → Use as SMTP sender.
→ zoho.com/mail