# Deployment guide

## Hosting

Use PHP 8.2+ and MySQL 8 with InnoDB. Point the web server document root to `public/`; never expose the repository root. Enforce HTTPS and keep logs, backups, and environment files outside the public directory.

## Checklist

- [ ] Set `APP_URL` to the HTTPS site origin, `APP_ENV=production`, and `APP_DEBUG=false`.
- [ ] Install PHP dependencies with `composer install --no-dev --optimize-autoloader`; keep `vendor/` outside the public web root.
- [ ] Enforce HTTPS at the web server/load balancer; production and staging session cookies are Secure even when TLS terminates upstream.
- [ ] Import schema only for a new database; create a limited MySQL application user rather than using `root`.
- [ ] Back up and restore-test existing data in a separate disposable database before applying reviewed schema changes.
- [ ] Keep the local installer unavailable in deployed environments; remove `INSTALLER_SECRET` and seed credentials after setup.
- [ ] Provision unique Administrator, Cashier, and Customer credentials securely; Cashiers can review GCash receipts while previously reviewed GCash orders remain visible as history.
- [ ] Keep demo accounts disabled by default. If a public demo is required, enable it only on an isolated demo deployment with separate database and no real customer/payment data; provision and mark all three accounts and use unique random passwords. `DEMO_CREDENTIALS_PUBLIC=true` exposes the configured credentials and allows demo login from any IP.
- [ ] Decide which customer payment methods to enable. Apply and verify database/07_payment_checkouts.sql and database/10_payment_deeplink.sql for Xendit, and database/09_manual_payment_reviews.sql for manual GCash review before enabling the relevant method.
- [ ] Configure official GCash merchant name/number and a public-assets QR image only if manual GCash is offered; configure COUNTER_PAYMENT_ENABLED according to store policy. Receipts are stored outside the web root.
- [ ] Ensure the private `storage/payment-receipts/` directory exists and is writable by the PHP worker, but not publicly readable. The project includes an Apache deny rule for `storage/`; still point the document root to `public/`.
- [ ] Xendit is optional. If online GCash is enabled, provide private provider credentials, activate the GCash channel, and test signed callbacks over HTTPS.
- [ ] Configure Gmail SMTP credentials in the private environment and test customer email OTP delivery, expiry, incorrect-attempt limit, resend cooldown, duplicate email, and unverified login/order denial in staging.
- [ ] For existing databases, apply `database/08_schema_email_verification.sql` if email verification columns are not present, then apply `database/11_email_otp_rate_limits.sql`.
- [ ] Configure Gmail SMTP and verify registration OTP and password-reset email delivery using a controlled staging mailbox.
- [ ] Implement and operationally test paid-order refunds/reconciliation before allowing cancellation of captured payments.
- [ ] Configure TLS, restricted file permissions, off-webroot encrypted backups, monitoring, and an error log outside the web root.
- [ ] Test authentication, role authorization, checkout, order lifecycle, reports, and database recovery in staging.

## Payment readiness

Online GCash uses the Xendit one-time `GCASH` Payments API request (`PH`/`PHP`), presents provider-returned web or mobile redirect actions, and creates the application order only after authenticated server-side verification. Manual GCash uses the merchant details configured in the private environment and remains unpaid pending cashier verification. Administrators can monitor all orders and verification history but cannot approve or reject manual payments. Browser return URLs, screenshots, and customer-submitted references are not payment proof. Xendit credentials are needed only when online GCash is enabled.

Before enablement:

1. Complete Xendit sign-up and use Test Mode. Live transactions require Xendit's business approval and the GCash channel to be activated. Follow the official [account creation](https://docs.xendit.co/docs/create-account.md), [verification](https://docs.xendit.co/docs/verifying-your-account.md), [Philippines requirements](https://docs.xendit.co/docs/philippines-business-documents.md), and [GCash channel](https://docs.xendit.co/docs/gcash.md) guidance.
2. Generate a restricted server-side API key, configure the active Xendit business ID, and register `public/xendit-webhook.php` in the Xendit dashboard. Enable Payment Requests v3 status events for capture, failure, payment expiry, and request expiry; configure and privately store the callback token.
3. Set the public HTTPS origin in `APP_URL`. Xendit cannot deliver callbacks to `localhost`; sandbox webhook tests require a reachable HTTPS staging endpoint or tunnel.
4. Set `XENDIT_ENABLED=true` only after applying `database/07_payment_checkouts.sql` and `database/10_payment_deeplink.sql`, activating GCash in the matching provider environment, and installing the API key, callback token, and business ID through the server environment/secret manager.
5. To offer manual GCash without Xendit, apply `database/09_manual_payment_reviews.sql`, set `GCASH_MANUAL_ENABLED=true`, and configure the official merchant name plus number and/or QR image path. Customer checkout does not offer counter payment; the setting is retained for compatibility with existing workflows.
6. Apply `database/12_payment_receipt_ocr.sql` and then `database/13_payment_duplicate_reference_flag.sql` to existing databases before deploying receipt review. Install Tesseract OCR and set private server variable `TESSERACT_PATH` to its executable for reference scanning; blank configuration checks common Windows paths and then the server PATH. Enable PHP GD and EXIF to preprocess/rotate images; without those extensions, Tesseract still tries multiple layout modes on the original image. Without Tesseract, the system still validates uploads, keeps the uploaded receipt available, and lets customers manually save a reference or retry OCR later. OCR is never payment proof.

The project creates only one payment-request attempt per saved checkout. If the request outcome is ambiguous due to a network timeout, do not retry it as a new charge; reconcile the reference in the Xendit dashboard. A verified webhook or status check creates the order idempotently, and a failed order insert can be retried from the saved checkout without a new charge. Preserve all existing payment columns, historical methods, and records.

## Production deployment steps

1. Create staging with a separate database and least-privilege MySQL account. Configure the web root as `public/`.
2. For a new database, review and import `database/01_schema_core.sql` through `database/07_payment_checkouts.sql`, then `database/10_payment_deeplink.sql`, `database/11_email_otp_rate_limits.sql`, `database/14_two_role_admin_payment_verification.sql`, and `database/15_cashier_demo_accounts.sql`; the latest `02_schema_orders.sql` already contains the receipt/OCR schema, so do not reapply migration `12`. Do not import migration `08` or `09` into a fresh schema. For existing data, take a backup and restore-test a separate copy, then apply the reviewed additive migrations needed for the application (`08` and `11` for email verification, payment migrations `07`, `09`, `10`, and `12` when applicable, followed by `14` and `15`). Do not run the local installer against populated/production data.
3. Provision Administrator, Cashier, and Customer accounts through the private setup procedure in `README.md` and `ADMINISTRATOR_CREDENTIALS_AND_DEPLOYMENT_SETUP.md`. For existing databases, apply migration 14 and then migration 15 after prerequisite schema migrations; verify legacy accounts remain disabled unless deliberately reassigned. Remove one-time bootstrap secrets.
4. Configure `APP_ENV=staging`, `APP_DEBUG=false`, HTTPS `APP_URL`, staging database values, email settings, enabled merchant payment options, and Xendit Test Mode API/webhook settings only if testing online GCash. Activate GCash in that environment and register a reachable HTTPS webhook when applicable.
5. Test customer registration/OTP, login/logout/recovery, menu/cart/address/checkout, payment verification, order status/history, Administrator/Cashier/Customer permissions, obsolete-role access denial, invalid inputs, and customer data isolation against staging. Verify Cashier cannot access other orders, walk-in creation, payment collection, user, product, report, or audit functions.
6. In sandbox, verify successful/pending/failed/canceled/expired outcomes, duplicate webhooks, mismatched amount/currency/reference/business, unauthenticated status requests, direct unpaid-order submissions, and order-insertion recovery after payment. Do not create live transactions without explicit operator approval.
7. Back up staging and restore to another disposable database. Validate row counts and application behavior without overwriting production.
8. Resolve remaining issues in `PRE_DEPLOYMENT_AUDIT_REPORT.md`, enforce HTTPS, implement password recovery and paid-refund procedures, and confirm monitoring and rollback.
9. Configure production secrets (`APP_ENV=production`, `APP_DEBUG=false`, HTTPS `APP_URL`, restricted `DB_*`, production email settings, official GCash merchant settings for manual transfers, and Xendit API key/webhook token/business ID only when online GCash has been approved and enabled). Never deploy `.env` or expose the repository root.
10. Verify health, protected routes, backups, and rollback readiness before opening ordering.

## Troubleshooting

- “Service is currently unavailable”: verify database host/name/user/password and MySQL reachability; inspect restricted server logs, never display them publicly.
- Online GCash is unavailable: verify migration 07, private Xendit values, active GCash channel, and callback URL/token. Manual GCash/counter availability is controlled independently by merchant configuration and COUNTER_PAYMENT_ENABLED.
- A checkout is unconfirmed: do not create another payment for the same reference. Check Xendit by the server-generated reference and reconcile an ambiguous API timeout before retrying.
- Registration OTP is not delivered: verify Gmail SMTP host/port, account username, App Password, sender address, outbound STARTTLS access, and restricted server logs.
- Registration is unavailable with SMTP configured: install project dependencies with Composer and confirm `vendor/autoload.php` exists on the server.
- Login or installer redirects/404s: verify the document root is `public/`, environment mode, and PHP session/log permissions. The installer is unavailable outside local mode.

## License

This project is intended for educational, academic, and portfolio use. Review any project-specific licensing restrictions before deployment or redistribution.
