# Buy Local Lowveld — Website (Phase 2b) Phase 2b adds PayFast payments, recurring memberships, cron-driven Mailchimp automations, rate limiting, and a minimal admin panel on top of the Phase 2a foundation. ## What's new since Phase 2a - **PayFast payments** — sandbox by default, trivially swapped to live - **One-off + recurring** — branding items = one-off, membership = annual subscription - **ITN webhook** (`payfast-itn.php`) with 4-layer verification: signature, source IP, amount match, server-to-server postback - **Payment-received event** → Mailchimp Flow E fires automatically on successful payment - **Renewal reminder cron** (Flow F) — tags members 30 days before `renewal_date` - **Overdue cron** (Flow G) — flags invoices past due + tags the member - **Cancel membership** (Flow H) — member-initiated flow with retention journey trigger - **Printable invoice** (`member/invoice.php`) — browser Save-as-PDF friendly - **Rate limiting** on login / signup / forgot-password - **Minimal admin panel** — mark invoices paid (EFT confirmations), view cron status ## Quick start ### 1. Database If you ran Phase 2a, just apply the additive migration: ```bash mysql -u buylocal -p buylocal < db/schema-2b.sql ``` Fresh install: ```bash mysql -u buylocal -p buylocal < db/schema.sql mysql -u buylocal -p buylocal < db/schema-2b.sql php db/seed.php ``` ### 2. Configuration Edit `includes/config.php`: ```php // Existing (from Phase 2a) define('MC_API_KEY', '...'); define('MC_AUDIENCE_ID', '...'); define('DB_HOST', 'localhost'); define('DB_NAME', 'buylocal'); define('DB_USER', 'buylocal'); define('DB_PASS', '...'); // Phase 2b additions define('PF_SANDBOX', true); define('PF_MERCHANT_ID', '10000100'); define('PF_MERCHANT_KEY', '46f0cd694581a'); define('PF_PASSPHRASE', 'jt7NOE43FZPn'); define('CRON_SECRET', '...'); // MUST change from default! ``` The PayFast sandbox values above are the public PayFast test account everyone uses. Switch to your own sandbox credentials once registered. ### 3. Run the crons Either add to the server crontab: ```cron 0 2 * * * php /path/to/buylocal/cron/renewal-reminder.php 30 2 * * * php /path/to/buylocal/cron/overdue.php ``` Or set up a URL pinger (UptimeRobot, cron-job.org, EasyCron) to visit: ``` https://yoursite.co.za/buylocal/cron/renewal-reminder.php?secret=YOUR_CRON_SECRET https://yoursite.co.za/buylocal/cron/overdue.php?secret=YOUR_CRON_SECRET ``` ## SOW event coverage — complete ✅ | SOW event | Status | |---|---| | New member | ✅ Phase 1/2a | | Newsletter signup | ✅ Phase 1 | | Contact form lead | ✅ Phase 1 | | Member updates info | ✅ Phase 2a (Flow D) | | **Member buys items** | ✅ **Phase 2b (Flow E)** — PayFast ITN confirmed | | **Renewal reminder 30d** | ✅ **Phase 2b (Flow F)** — daily cron | | **Payment overdue** | ✅ **Phase 2b (Flow G)** — daily cron | | **Cancellation** | ✅ **Phase 2b (Flow H)** — member-initiated + retention journey | ## Testing a payment end-to-end 1. Log in as any demo member (any listing email + `demo1234`) 2. Go to dashboard → click "Renew / re-subscribe" 3. Land on PayFast sandbox — use their test card `4000 0000 0000 0002` 4. Complete the payment 5. You'll bounce back to `payment-return.php` 6. Within seconds, `payfast-itn.php` receives the ITN 7. Check your `payments` DB table — one row, `processed = 1` 8. Check `mailchimp_sync_log` — row with `event = payment_received` 9. Check your Mailchimp audience — the member now has `Payment Received` tag ## File additions over Phase 2a ``` db/schema-2b.sql additive migration includes/ rate-limit.php per-IP counter payfast.php sig gen/verify + URLs payfast-itn.php webhook receiver member/ checkout-cart.php one-off redirect checkout-membership.php recurring redirect payment-return.php post-payment landing payment-cancel.php cancellation landing invoice.php printable invoice/receipt cancel-membership.php Flow H cron/ _bootstrap.php auth + cron_runs logging renewal-reminder.php Flow F overdue.php Flow G admin/ _guard.php shared admin gate _footer.php invoices.php mark-as-paid for EFT cron-status.php cron observability docs/ PAYFAST-INTEGRATION.md the whole payment story ``` ## Security additions in 2b - All auth endpoints rate-limited (10 login / 5 signup / 5 forgot per 15 min per IP) - ITN 4-layer verification (signature + IP + amount + postback) - Idempotency on ITN replays (`pf_payment_id` unique) - Cron scripts require `CRON_SECRET` if hit over HTTP - `admin/*` gated behind `auth_require_admin()` ## What's new in Phase 2c - **Admin dashboard** (`admin/dashboard.php`) with three SOW widgets: new members (last 3 months), members with unpaid invoices (last month), and active carts - **Member list** (`admin/members.php`) — search by email/business/name, filter by status/tier - **Member edit** (`admin/member-edit.php`) — change any field; changes sync to Mailchimp automatically; status='cancelled' fires the Cancelled lifecycle event - **Mailchimp re-sync button** on each member — push current DB record to Mailchimp without the member logging in - **Business claim approvals** (`admin/claims.php`) — approve/reject pending claims with emailed notification either way - **Mailchimp sync log viewer** (`admin/mailchimp-log.php`) — filterable audit trail of every sync event, answering "did X's change actually reach Mailchimp?" ## What's still deferred - Full member list + edit in admin - Claim-business approval workflow in admin - Admin dashboard widgets (new members, unpaid, cart) - Mailchimp sync log viewer - Bulk operations (send newsletter, export members) - Proper SMTP (Mailgun / SES / Mailchimp Transactional) - WhatsApp / SMS if still wanted ## Known limitations - **Public PayFast sandbox doesn't cancel subscriptions via API.** The cancel flow updates our DB but leaves PayFast to keep trying to charge. Swap to your own merchant credentials to test real cancellation. - **`mail()` delivery varies** by host. If members don't get password-reset / temp-password emails, upgrade `includes/mail.php` to SMTP. - **Journey IDs are still `0`** for the new Phase 2b journeys. Build them in the Mailchimp UI and paste the IDs in `config.php` — tags will apply either way, only email sequences need the journeys. - **PDF generation uses the browser.** `member/invoice.php` has print-ready CSS; hit "Save as PDF" from the browser print dialog. Server-side PDF generation would need Composer + Dompdf. ## Reference - `docs/SYSTEM-MAP.md` — file layout + request flows - `docs/MAILCHIMP-FLOWS.md` — per-event diagrams (now Flows A–H all live) - `docs/DB-SCHEMA.md` — table-by-table reference - `docs/PAYFAST-INTEGRATION.md` — payment architecture + going-live checklist