# Conversion status

## Source audit

| Area | Supplied source |
| --- | ---: |
| Framework | CodeIgniter 4.7.4 |
| Product version | Modesy 2.7 |
| Route statements | 294 |
| Controllers | 24 |
| Models | 35 |
| Views | 393 |
| Database tables | 100 |
| Asset files | 430 |

## Architecture decisions

- Laravel 12 with a PHP 8.2 floor.
- Preserve the legacy schema and data during conversion.
- Use Eloquent adapters against legacy tables before optional normalization.
- Keep multi-vendor behavior enabled by default and configurable.
- Separate installation licensing from licenses sold to marketplace customers.
- Use named REST-style routes; preserve old public URLs through compatibility redirects after every route is mapped.

## Module progress

| Module | Audit | Models | Routes | Controllers/services | Views | Tests |
| --- | --- | --- | --- | --- | --- | --- |
| Application foundation | Done | N/A | Done | Started | Started | Pending |
| Users and legacy roles | Done | Done | Done | Done | Done | Started |
| Catalog and product translations | Done | Done | Done | Done | Started | Pending |
| Cart and checkout | Done | Done | Done | Done | Started | Started |
| Orders and order items | Done | Done | Done | Done | Started | Pending |
| Seller marketplace | Done | Done | Done | Done | Started | Started |
| Admin | Done | Started | Started | Started | Started | Started |
| WebMarket product licensing | Started | Started | API started | Verification started | Pending | Pending |
| Payments and callbacks | Done | Done | Done | Done | Started | Started |
| Shipping and tax | Done | Done | Done | Done | Done | Started |
| Digital delivery | Done | Done | Done | Started | Started | Pending |
| Seller payouts | Done | Done | Done | Done | Done | Started |
| Customer wallet | Done | Done | Done | Done | Done | Started |
| Membership and promotions | Done | Done | Done | Done | Done | Started |
| Storefront engagement and coupons | Done | Done | Done | Done | Done | Started |
| Newsletter, email queue, reports, attachments | Done | Done | Done | Done | Done | Started |
| Affiliate attribution and settlement | Done | Done | Done | Done | Done | Started |
| Operational parity and media | Done | Done | Done | Done | Done | Started |
| Blog, pages, SEO, feeds | Done | Done | Done | Done | Done | Started |
| Support, chat, and notifications | Done | Done | Done | Done | Done | Started |
| Localization and dynamic routes | Done | Done | Done | Done | Done | Started |
| Legacy data and uploads importer | Done | N/A | N/A | Done | N/A | Started |

## Database upgrade order

1. Back up the existing database and `uploads` directory.
2. Import the supplied `modernstore.sql` into an empty database.
3. Configure the Laravel `.env` connection.
4. Run `php artisan webmarket:legacy-audit`. Do not continue unless it reports `YES`.
5. Run `php artisan migrate` to add Laravel infrastructure and licensing tables.
6. Run the audit again and retain its JSON output with `php artisan webmarket:legacy-audit --json`.

The Laravel migrations are additive. They do not delete, rename, truncate, or reseed the 100 legacy tables.

## Authentication compatibility completed

- Existing `password_hash()` values are accepted by Laravel without resetting user passwords.
- Existing verified users are backfilled from `email_status` to `email_verified_at`.
- New registration, login throttling, logout, password reset, and signed email verification routes are present.
- Old form POST URLs and outstanding `confirm-account?token=...` links remain temporarily compatible.
- The database audit blocks cutover when duplicate non-placeholder login emails exist.
- Login activity continues to populate the legacy `user_login_activities` table.

## Commerce safeguards completed

- Guest and authenticated carts use the existing `carts` and `cart_items` tables.
- Guest carts merge into a user's existing cart on login without duplicate line items.
- Product availability, ownership, quantity, stock, and current price are checked server-side.
- Money totals use integer minor units rather than binary floating-point calculations.
- Checkout creation locks and revalidates products and stores an immutable checkout-item snapshot.
- Earlier active checkouts are expired before a replacement checkout is created.
- Customer order and seller sales pages enforce buyer/seller ownership through policies.
- Products use reversible legacy soft-deletion flags instead of deleting database records.

## Online payment and fulfillment safeguards completed

- Paystack and Flutterwave initialization and verification use Laravel's server-side HTTP client.
- Secrets are environment-only and are never placed in a Blade template or browser request.
- Paystack validates `x-paystack-signature` against the raw request body using HMAC-SHA512.
- Flutterwave validates `flutterwave-signature` against the raw request body using HMAC-SHA256.
- A signed webhook still triggers a provider API re-query; signatures alone never fulfill an order.
- Verified reference, status, exact amount, and currency must all match the locked checkout.
- Webhook event IDs are unique and encrypted payloads are retained for troubleshooting.
- Duplicate callbacks return the already-paid order without repeating stock, earnings, downloads, invoices, or licenses.
- Order, item, transaction, stock, seller earning, invoice, digital sale, and WebMarket license creation share one database transaction.
- Purchased files are served only after buyer ownership and paid-order checks; paths use `basename()` and a private legacy filesystem disk.

## Offline payment and refund safeguards completed

- Bank-transfer orders remain unpaid until an administrator reviews a privately stored receipt.
- Cash on delivery is restricted to physical goods and respects each seller's COD setting and debt limit.
- Physical inventory is reserved when an offline order is placed and restored exactly once if an unpaid order is cancelled.
- Administrator approval uses the same locked, idempotent fulfillment path as verified online payments.
- Transaction, bank-transfer, COD collection, refund completion, and related state changes are retained in an administrator audit log.
- Buyers can open item-level refund requests; buyers, sellers, and administrators can communicate on the request.
- Only pending refund requests can be accepted or declined, and only accepted requests can be completed.
- Refund completion records an external provider/bank reference and marks the order item and seller earning as refunded.
- The application deliberately does not claim to move money: administrators must execute the external refund before recording completion.

## Shipping and tax safeguards completed

- Existing countries, states, customer addresses, seller zones, zone locations, delivery methods, and tax records remain authoritative.
- Physical carts match seller delivery zones in deterministic state, country, then continent order.
- Each seller in a multi-vendor cart requires its own valid delivery method; a client cannot submit an arbitrary price or method ID.
- Flat delivery supports per-order, per-item, and validated weight-range pricing, plus local pickup and threshold-based free shipping.
- Checkout revalidates product prices, stock, VAT, destination, delivery methods, tax scope, and totals inside the database transaction.
- Product VAT and global location taxes use integer minor-unit calculations and are snapshotted before payment initialization.
- Delivery cost is assigned once per seller rather than once per line item, preventing duplicate seller earnings.
- Sellers can maintain their own zones and rates; administrators can maintain active tax rules through Bootstrap 5 screens.
- Saved addresses are optional, while the immutable checkout and resulting order retain the address actually used.

## Seller earnings and payout safeguards completed

- A paid non-COD order credits the seller balance only when its unique earning record is first created.
- Commission is calculated on the sale before VAT; VAT and the seller's one-time delivery amount are then included in earnings.
- Cash-on-delivery sales accrue marketplace commission debt instead of incorrectly crediting money already collected by the seller.
- Refund completion reverses the matching available balance or COD commission debt before marking the earning refunded.
- New payout requests lock the seller row, verify the enabled method and minimum, and reserve the balance immediately to prevent duplicate withdrawals.
- Rejected payouts return reserved funds exactly once; approved payouts require an external payment reference and are audit logged.
- Pre-conversion pending payouts remain compatible: their balance is safely deducted during approval because they were not originally reserved.
- The legacy integer commission-debt column is widened to `DECIMAL(12,2)` so fractional currency amounts are not silently lost.

## Wallet, membership, and promotion safeguards completed

- New wallet credits and debits are recorded in an immutable ledger while the original deposit and expense tables remain populated for compatibility.
- Paystack and Flutterwave wallet deposits pass through the same signature, API re-query, exact-reference, amount, currency, and idempotency checks as order payments.
- A wallet-funded checkout locks the customer balance, records one debit, and can fulfill an order, membership, or promotion without double spending.
- Wallet-funded item refunds automatically credit the buyer ledger exactly once; external provider and bank refunds still require a real external reference.
- Seller earnings, payout reservations, payout reversals, and earning refunds now also appear in the ledger, keeping all new balance changes traceable.
- Membership payments snapshot their plan, taxes, price, duration, and product allowance; free-plan reuse follows the preserved site setting.
- Product creation enforces active membership duration and listing limits on the server rather than relying on hidden buttons.
- Product promotions use administrator-maintained daily/monthly prices, extend an existing future expiry, and receive priority in catalog ordering only while active.
- Service checkout fulfillment has a unique token guard, so repeated callbacks cannot activate a plan or extend a promotion twice.
- `webmarket:maintain` runs hourly through Laravel's scheduler to expire completed memberships and promotions without Redis or a resident worker.

Still pending in the commerce phase: PHP runtime integration tests, provider sandbox exercises, currency conversion for non-base shipping rates, bank-transfer service payments, and advanced promotion analytics.

## Communication safeguards completed

- Existing `support_tickets`, `support_subtickets`, `chat`, and `chat_messages` rows remain authoritative and retain their original status and participant fields.
- Customer ticket pages enforce ownership; administrator pages can filter, reply, and change status without exposing another customer's thread.
- Ticket state changes are serialized under a database lock, and closed tickets reject additional replies until an administrator reopens them.
- Marketplace conversations are visible and writable only to their two participants; recipient IDs are derived on the server for every reply.
- Removing a conversation preserves the legacy per-user deletion contract: the first participant hides it, and data is physically removed only after both participants delete it.
- New messages and support replies use queued Laravel notifications with database alerts; chat email respects the legacy `send_email_new_message` preference.
- Notification redirects are limited to this application's own URL, and support/chat submission endpoints are rate limited.
- The database queue, scheduler, and standard cron worker remain deployable without Redis or a resident process manager.

Still pending in the communication phase: attachment upload conversion, guest support tickets, outbound legacy `email_queue` draining, and PHP integration tests.

## Public content and SEO safeguards completed

- Existing multilingual `blog_categories`, `blog_posts`, `blog_tags`, `blog_comments`, and `pages` data is used directly without destructive schema conversion.
- Administrators can maintain blog posts, categories, tags, comments, custom pages, menu placement, visibility, and page-level SEO fields through Bootstrap 5 screens.
- System pages remain editable but cannot be deleted; categories containing posts must be emptied before deletion.
- Public blog routes support category and tag filtering, approved comments, canonical URLs, Open Graph fields, descriptions, and keywords.
- Comment availability and moderation continue to respect the legacy `blog_comments` and `comment_approval_system` settings, with public submission rate limiting.
- `sitemap.xml` is a dynamic sitemap index with separate page, post, and product maps capped at the protocol limit of 50,000 URLs each.
- `robots.txt` advertises the sitemap and excludes account, seller, and administrator areas from crawling.
- `feed.xml` provides a standards-compatible RSS feed only while the preserved `rss_system` setting is enabled.
- Dynamic sitemap and feed responses avoid public-directory write permissions and are suitable for shared hosting.

Still pending in the content phase: rich-text editor integration, image upload management, multilingual URL prefixes/hreflang, and PHP integration tests.

## Localization and migration safeguards completed

- Active secondary languages receive validated `/{locale}/...` storefront, category, product, blog, tag, comment, and page routes while the default language keeps clean unprefixed URLs.
- Product details, category translations, blog records, content pages, navigation links, and canonical URLs prefer the active language without duplicating database content.
- Locale codes must match an enabled row in the preserved `languages` table; arbitrary prefixes cannot change application locale.
- A controlled legacy fallback reads the preserved 76-key route dictionary and redirects supported product, category, blog, order, cart, wallet, download, refund, RSS, message, and ticket URLs to named Laravel routes.
- Compatibility redirects use a fixed allowlist and cannot become open redirects; Laravel route caching never depends on executing database queries during route registration.
- `webmarket:import-uploads` supports dry-run, SHA-256 comparison, conflict detection, verification, executable-file blocking, and an immutable private manifest while retaining `uploads/...` paths.
- `webmarket:legacy-integrity` reports duplicate public identifiers and orphaned commerce, content, chat, cart, and support relations without modifying records.
- The public storage link includes both Laravel's standard `/storage` path and a legacy `/uploads` compatibility path.
- A Windows and cPanel migration runbook documents safe cutover order and explicitly prohibits destructive migration commands.

Still pending in this phase: live PHP execution, route-cache verification, full-size production upload timing, resolving any integrity issues reported by the supplied data, and translated `hreflang` discovery across equivalent content records.

## Storefront engagement safeguards completed

- Authenticated wishlists reuse the original `wishlist` table, use product locks for idempotent toggles, and never accept a user ID from the browser.
- Product ratings are limited to one review per authenticated buyer and require a paid order containing the reviewed product; sellers cannot review their own listings.
- Product questions support moderated guest or member submissions and validated same-product replies using the preserved comment settings.
- Seller profiles expose only published products and privacy-approved profile fields; follow and unfollow actions enforce identity and seller status.
- Abuse reports require authentication, reject self-reporting and duplicates, and are available to administrators alongside product-comment and review moderation.
- Contact messages are validated, rate limited, stored in the legacy table, escaped in administration screens, and removable after processing.
- Multilingual knowledge-base categories and articles have public search/browse routes plus administrator maintenance screens.
- Seller coupons retain product scopes, currency, expiry, minimum amount, usage type, and maximum count; sellers can select only their own products.
- Coupons are locked and revalidated during checkout, discount only eligible seller items, reduce applicable VAT and seller earnings, and create idempotent usage history with the final order.
- Cancelling an unpaid order releases its coupon usage; paid orders retain immutable coupon details and discounted item totals for invoices and refunds.

Still pending in this phase: guest-session wishlists, review attachments, contact-message reply tracking, coupon analytics, and live concurrency tests.

## Operations and communication hardening completed

- Newsletter signup and token-based unsubscribe reuse the preserved `subscribers` table and rate-limit public requests.
- Administrator broadcasts are queued into the original `email_queue` table in bounded database batches, excluding legacy blacklist entries.
- `webmarket:send-email-queue` sends bounded batches, removes only successful or blacklisted rows, logs failures without exposing message contents, and runs every five minutes through the existing shared-hosting scheduler.
- Support attachments are restricted to three allowlisted files of at most 5 MB each, stored on the private disk, and downloadable only by the ticket owner or an administrator.
- Administrator reporting provides bounded date-range revenue, paid-order, customer, product, coupon, and top-product summaries using MySQL/MariaDB-compatible SQL.
- `webmarket:deployment-check` performs non-destructive PHP, extension, configuration, permission, database, and required-table checks before shared-hosting cutover.

Still pending in this phase: newsletter campaign history, CSV report exports, antivirus scanning, and live SMTP/runtime tests.

## Affiliate safeguards completed

- Sellers explicitly select eligible products and a bounded 1–50% referral rate; another user cannot enable products belonging to a different seller.
- Authenticated members generate opaque reusable links only for eligible products and cannot refer their own products.
- Referral clicks store only the link identity and a server-generated 30-day expiry; commission rates and participant IDs are reloaded from authoritative database rows during checkout.
- Checkout accepts attribution only when the exact eligible product remains in the cart and rejects self-referrals, seller referrals, expired sessions, disabled products, and zero rates.
- The validated referral identity, product, seller, referrer, rate, and capture time are snapshotted into checkout and then the order, preventing later setting changes from rewriting order history.
- Paid-order settlement deducts referral commission from the seller's net earning and credits the referrer's wallet and immutable ledger in the same locked transaction.
- Affiliate earning creation is idempotent by referrer, order, and product; duplicate payment callbacks do not repeat seller deductions or wallet credits.
- Cash-on-delivery settlement includes referral commission in seller commission debt, while the referrer receives the same traceable wallet credit after collection approval.
- Completed item refunds debit the affiliate wallet exactly once, reverse the COD liability where applicable, and retain the original affiliate earning for audit history.

Still pending in the affiliate phase: click/impression analytics, multi-touch attribution, site-funded commissions, affiliate discounts, CSV exports, and live concurrency tests.

## Operational parity safeguards completed

- Affiliate referral visits now record privacy-preserving HMAC visitor fingerprints, source host, link, product, referrer, and timestamp in an additive analytics table without retaining raw IP addresses.
- Repeated clicks for the same link in one browser session are deduplicated, and affiliates can view click totals and export their own earning history as CSV.
- Guest customers can open and revisit support tickets through a 64-character private token; only its SHA-256 hash is retained in the database and invalid tokens return a generic not-found response.
- Guest support creation and replies are rate limited, existing administrator support screens continue to include guest conversations, and closing remains controlled by support staff.
- Sellers can upload JPEG, PNG, or WebP product images with server-side MIME validation, a 10 MB per-file limit, ownership authorization, private mutation routes, and safe public delivery paths.
- Product image deletion removes managed files, preserves unrelated files, and promotes the next image when the deleted image was the primary image.
- A dependency-free rich-text toolbar enhances long product and content fields while preserving the underlying textarea; seller product HTML and administrator blog HTML pass through an allowlist sanitizer before storage.
- The new analytics and guest-token schema migration is additive and reversible; no original marketplace column or record is renamed or replaced.

Still pending in this phase: image resizing/web optimization, guest email notifications, malware scanning, impression analytics, and live PHP/browser tests.

## Production acceptance framework completed

- The installer now uses native HTML form submission and no longer depends on JavaScript to copy database, administrator, company, or license fields.
- Windows PowerShell and Linux/cPanel acceptance runners stop on the first failed dependency, schema audit, migration status, integrity, route, PHPUnit, or deployment check.
- Acceptance runners recreate Laravel runtime directories, while explicit view configuration prevents the `View path not found` failure seen after ZIP extraction.
- Acceptance runners execute additive migrations only after a successful legacy safety audit, then verify migration status; this supports imported databases that do not yet contain Laravel's migration repository.
- Legacy duplicate-identifier checks select only grouped identifiers and aggregate counts, remaining compatible with MySQL/MariaDB `ONLY_FULL_GROUP_BY` strict mode.
- Installer and runtime-license services use constructor injection in middleware, matching Laravel's two-argument middleware pipeline contract and preventing browser/test `ArgumentCountError` failures.
- Installer controller services also use constructor injection, preventing optional `{step?}` route values from displacing service parameters during Laravel controller dispatch.
- The public-storage readiness check clears PHP's stat cache and verifies that the resolved Windows junction or Unix symlink targets the actual `storage/app/public` directory.
- Production readiness now also requires HTTPS, a disabled installer, an installation lock, complete enabled-license configuration, and public storage availability.
- Installer access tests cover disabled, unlocked, and already-installed states.
- Runtime license tests cover cached successful verification and fail-closed explicit rejection.
- A business acceptance matrix covers accounts, catalogue, sellers, checkout, gateways/webhooks, fulfilment, downloads, refunds, content, support, affiliates, and licensing.
- The cutover checklist requires a disposable rehearsal, final backups, verified uploads, scheduler/queue configuration, a low-value live payment, and a retained rollback release.

Still pending before production declaration: execute the automated runner on PHP 8.2/MySQL, complete every manual workflow item in a browser, resolve any runtime failures, and record the final acceptance evidence.

## Important distinctions

The legacy `product_license_keys` table stores license keys bundled with digital products. The new `licenses` and `license_activations` tables represent licenses issued and verified by WebMarket. They must not be merged without an explicit migration because their responsibilities differ.

## Runtime verification

The current conversion workspace does not provide PHP or Composer. PHP execution, Composer dependency resolution, Artisan route inspection, migrations, and PHPUnit remain mandatory before this build can be declared deployable.
