# WooCommerce SMS Services — Authentication Implementation

Reference guide for the Mobile OTP authentication system built into this plugin.

**Plugin path:** `wp-content/plugins/woocoomerce-sms-services`  
**Admin settings:** `wp-admin/admin.php?page=woocommerce-sms-services-settings`  
**Requires:** WooCommerce 6.0.0+ (`Requires Plugins: woocommerce`)  
**Last updated:** July 7, 2026 (checkout OTP verification removed)

---

## Overview

This plugin is a **WooCommerce SMS plugin** with an **additive** OTP authentication layer. It does not replace or duplicate the SMS gateway. All OTP messages are sent through the existing `send_sms_message()` wrapper.

Authentication integrates directly into native WooCommerce pages:

| Feature | WooCommerce page | Custom pages | Shortcodes |
|---------|------------------|--------------|------------|
| Login | My Account | No | No |
| Registration | My Account (register column) | No | No |
| Forgot password | My Account → Lost password | No | No |

**Existing functionality unchanged:** SMS gateway, order-status SMS, test SMS, SMS templates (order), How-to notification toggles.

---

## Plugin bootstrap and WooCommerce compatibility

The plugin is designed to work on any supported WooCommerce installation without code changes.

### Bootstrap sequence

```
plugins_loaded:20  → wc_sms_bootstrap_plugin()
                   → checks WooCommerce is active
                   → loads Woocoomerce_Sms_Services

before_woocommerce_init → HPOS + cart_checkout_blocks compatibility declared
```

### Compatibility class

**Class:** `includes/class-woocoomerce-sms-woocommerce-compatibility.php` (`Woocoomerce_Sms_WooCommerce_Compatibility`)

Centralizes runtime detection so the plugin does not hard-code WooCommerce version assumptions.

| Method | Purpose |
|--------|---------|
| `is_woocommerce_active()` | WooCommerce plugin is loaded |
| `get_woocommerce_version()` | Installed WC version (`WC_VERSION`) |
| `meets_minimum_version()` | WC >= 6.0.0 |
| `ensure_wc_session()` / `persist_wc_session()` | Shared WC session helpers for guest AJAX |
| `get_admin_compatibility_notices()` | Admin error messages when WooCommerce is missing or outdated |

### Declared WooCommerce compatibility

Declared in `woocoomerce-sms-services.php` via `before_woocommerce_init`:

| Feature | Declaration |
|---------|-------------|
| HPOS (custom order tables) | `FeaturesUtil::declare_compatibility( 'custom_order_tables', …, true )` |
| Cart / Checkout Blocks | `FeaturesUtil::declare_compatibility( 'cart_checkout_blocks', …, true )` |

Auth flows do not write to order tables directly.

---

## Mobile number input (intl-tel-input)

All authentication flows use a **country code dropdown + mobile number field** powered by the bundled [intl-tel-input](https://github.com/jackocnr/intl-tel-input) library (v23.9.3). OTP and SMS gateway logic are unchanged — only phone capture, validation, and storage were improved.

### UI

Each mobile field renders as:

```
| 🇮🇳 +91 ▼ | 7974208129 |
```

- Country flags and dial codes in the dropdown (all countries).
- Default country from WooCommerce store base location (`wc_get_base_location()`).
- Responsive layout; styles match WooCommerce form fields (`woocommerce-Input`, `input-text`).

### Storage format (E.164)

Numbers are normalized to **E.164** before AJAX submission and database storage.

| Input | Stored / sent |
|-------|----------------|
| Country: +91, Number: 7974208129 | `+917974208129` |

`Woocoomerce_Sms_Otp::normalize_mobile()` strips non-digit characters (except a leading `+`) and collapses international prefixes.

### Backward compatibility

`get_user_by_mobile()` and `get_mobile_lookup_variants()` search multiple formats so existing users registered without a country code still work:

- E.164 (`+917974208129`)
- Digits only (`917974208129`)
- Legacy 10-digit local (`7974208129`)
- Other variants derived from India `+91` patterns

### Class and assets

**Class:** `public/class-woocoomerce-sms-phone.php` (`Woocoomerce_Sms_Phone`)

| Method | Purpose |
|--------|---------|
| `render_field()` | Renders shared phone partial for login, register, forgot password |
| `register_assets()` / `enqueue_assets()` | Loads intl-tel-input CSS/JS + `woocoomerce-sms-phone.js` |
| `get_default_country_iso2()` | Store base country (ISO2, e.g. `in`) |

**JS API:** `window.wcSmsPhone`

| Method | Purpose |
|--------|---------|
| `init()` / `initAll()` | Initialize intl-tel-input on `.wc-sms-intl-phone` fields |
| `getNumber(selector)` | Returns E.164 from intl-tel-input (used by all AJAX handlers) |
| `isValid(selector)` | Client-side validity check before send OTP |

### Where phone input is used

| Flow | Field ID | Submission |
|------|----------|------------|
| Login | `#wc-sms-mobile` | AJAX via `wcSmsPhone.getNumber()` |
| Registration | `#wc_sms_mobile` | Hidden `wc_sms_mobile` synced on form submit |
| Forgot password | `#wc-sms-forgot-mobile` | AJAX via `wcSmsPhone.getNumber()` |

### Phone-related files

| File | Purpose |
|------|---------|
| `public/class-woocoomerce-sms-phone.php` | Asset enqueue + `render_field()` |
| `public/partials/woocoomerce-sms-phone-field.php` | Shared tel + hidden field markup |
| `public/js/woocoomerce-sms-phone.js` | intl-tel-input init and E.164 helpers |
| `public/vendor/intl-tel-input/` | Bundled library (JS, CSS, flag images, utils) |

---

## Store owner setup

1. Install and activate **WooCommerce** (6.0.0 or newer).
2. Install and activate the plugin.
3. **WooCommerce SMS → General Settings** — configure SMS gateway (`api_request_url`, `access_key`, `s_id`).
4. **WooCommerce SMS → Authentication** — enable features and OTP configuration.
5. **WooCommerce SMS → SMS Settings** — configure Login and Forgot Password OTP templates.
6. **WooCommerce → Settings → Accounts & Privacy** — enable *Allow customers to create an account on the "My account" page* (required for registration).

No pages, shortcodes, or template copying required.

### Admin compatibility notices

Notices are shown only when there is a genuine problem. They are generated by `Woocoomerce_Sms_WooCommerce_Compatibility::get_admin_compatibility_notices()`:

| Condition | Notice |
|-----------|--------|
| WooCommerce not active | Plugin requires WooCommerce |
| WooCommerce < 6.0.0 | Shows minimum required version and installed version |

### URLs (automatic)

| Flow | URL |
|------|-----|
| Login / Register | `/my-account/` |
| Lost password | `/my-account/lost-password/` |

---

## Feature 1 — Login via Mobile + OTP

**Setting:** `wc_sms_enable_mobile_login` = `yes`

### Flow

```
Mobile Number → Send OTP → SMS → Enter OTP → Verify → wp_set_auth_cookie() → My Account
```

### Behaviour

- Replaces the default WooCommerce username/password login form on **My Account** via template override.
- The login column renders only the mobile OTP UI (no username, password, remember me, or WC login button).
- Mobile field uses intl-tel-input (country code + number); AJAX sends normalized **E.164** via `wcSmsPhone.getNumber('#wc-sms-mobile')`.
- Password login is blocked server-side:
  - `woocommerce_process_login_errors` — blocks WC login form processing
  - `authenticate` filter (priority 30) — blocks password authentication on My Account
- User is looked up by mobile number via `Woocoomerce_Sms_Otp::get_user_by_mobile()`:
  - `mobile_number` user meta
  - `billing_phone` user meta
  - `user_login` (when mobile is the username)
- On successful OTP verification, user lookup falls back to `get_user_by_mobile()` if the OTP record has no user ID.
- On successful login, `mobile_number` meta is synced if missing.
- Login uses `wp_set_auth_cookie()` after OTP verification.

### Integration

| Hook / endpoint | Purpose |
|-----------------|---------|
| `woocommerce_locate_template` | Overrides `myaccount/form-login.php` when enabled |
| `woocommerce_process_login_errors` | Blocks WC login form submission |
| `authenticate` (priority 30) | Blocks password-based auth on My Account |
| `wc_sms_send_login_otp` | AJAX — send OTP |
| `wc_sms_verify_login_otp` | AJAX — verify OTP and log in |
| `wc_sms_resend_login_otp` | AJAX — resend OTP |

### Files

- `public/class-woocoomerce-sms-template-loader.php`
- `public/class-woocoomerce-sms-auth.php`
- `public/class-woocoomerce-sms-phone.php`
- `public/templates/woocommerce/myaccount/form-login.php`
- `public/partials/woocoomerce-sms-login-form.php`
- `public/partials/woocoomerce-sms-phone-field.php`
- `public/js/woocoomerce-sms-auth.js`
- `public/js/woocoomerce-sms-phone.js`
- `public/css/woocoomerce-sms-auth.css`

---

## Feature 2 — Registration

**Setting:** `wc_sms_enable_mobile_login` = `yes` (registration is enabled with mobile login)

### Flow

```
Name + Email + Mobile + Password → Register → WooCommerce customer created → mobile_verified = 0
```

No OTP is sent during registration. `mobile_verified` is set to `0` at signup.

### Registration form fields

Fields render in this order on the My Account register form:

| Order | Field | Source |
|-------|-------|--------|
| 1 | **Name** | Plugin — `woocommerce_register_form_start` (`wc_sms_name`) |
| 2 | **Email** | Native WooCommerce register form (`reg_email`) |
| 3 | **Mobile Number** | Plugin — `wc_sms_register_form_after_email` (`wc_sms_mobile`); intl-tel-input country code + number; stored as E.164 |
| 4 | **Password** | Native WooCommerce register form (`reg_password`), when WooCommerce is set to *not* auto-generate passwords |

If WooCommerce is configured to auto-generate passwords (`woocommerce_registration_generate_password` = `yes`), the password field is hidden by WooCommerce and a password is generated automatically.

The **username field is not rendered** in the login template override. Mobile number is used as `user_login` via `woocommerce_new_customer_data` and `pre_user_login` filters.

### Saved data

| Key | Value |
|-----|-------|
| `user_login` | Mobile number (E.164 normalized) |
| `mobile_number` | Mobile number (E.164) |
| `mobile_verified` | `0` |
| `billing_phone` | Mobile number (E.164) |
| `first_name` / `display_name` | Name from registration |

### Integration

| Hook | Purpose |
|------|---------|
| `woocommerce_register_form_start` | Adds Name field |
| `wc_sms_register_form_after_email` | Adds Mobile field after email (fired in `form-login.php` template) |
| `woocommerce_process_registration_errors` | Validates name, email, mobile, and password |
| `woocommerce_registration_errors` | Same validation inside `wc_create_new_customer` |
| `woocommerce_new_customer_data` | Sets `user_login`, `first_name`, `display_name` |
| `pre_user_login` | Ensures mobile is used as username |
| `woocommerce_created_customer` | Saves `mobile_number`, `mobile_verified`, `billing_phone` |

### Files

- `public/class-woocoomerce-sms-registration.php`
- `public/class-woocoomerce-sms-phone.php`
- `public/partials/woocoomerce-sms-phone-field.php`
- `public/js/woocoomerce-sms-phone.js`
- `public/templates/woocommerce/myaccount/form-login.php` (register column + `wc_sms_register_form_after_email` hook)

---

## Feature 3 — Forgot password via SMS OTP

**Setting:** `wc_sms_enable_forgot_password_otp` = `yes`

### Flow

```
Mobile Number → Send OTP → Verify OTP → New Password → Confirm Password → Update Password → Logged in
```

### Behaviour

- Replaces the default WooCommerce email-based lost password form via template override.
- Only the SMS OTP reset UI is rendered (no username/email fields, no email description).
- Mobile field uses intl-tel-input; AJAX sends normalized **E.164**.
- Email password reset is blocked (`lostpassword_post`, `send_retrieve_password_email`, `template_redirect`).
- WooCommerce `wc-lost-password` script is dequeued on the lost-password page to prevent conflicts with the OTP UI.
- After OTP verification, verified user ID and mobile are stored in **WooCommerce session** before the password step.
- Session is **persisted immediately** after OTP verify via `Woocoomerce_Sms_WooCommerce_Compatibility::persist_wc_session()`.
- Password reset validates session with `reset_mobiles_match()` (E.164 + legacy lookup variants).
- Session is cleared **only after** successful password update, or when a new OTP send starts.
- On verify success, JS syncs `#wc-sms-forgot-mobile-hidden` with the server-returned normalized mobile.
- Password is updated with `wp_set_password()`; user is logged in via `wp_set_auth_cookie()`.
- Final submit button label is **Update Password**.

### Session lifecycle

```
Send OTP     → clear_reset_session() (discard any prior verify state)
Verify OTP   → set_reset_session(user_id, mobile) → persist_wc_session()
Update Pass  → read session → wp_set_password() → clear_reset_session() → wp_set_auth_cookie()
```

### Session helpers (`Woocoomerce_Sms_Forgot_Password`)

| Method | Purpose |
|--------|---------|
| `set_reset_session()` | Stores verified user ID + E.164 mobile; calls `persist_wc_session()` |
| `get_reset_user_id()` / `get_reset_mobile()` | Reads session on password update |
| `clear_reset_session()` | Clears keys after password update or new OTP send |
| `persist_wc_session()` | Delegates to `Woocoomerce_Sms_WooCommerce_Compatibility::persist_wc_session()` |
| `reset_mobiles_match()` | Compares POST mobile to session mobile using lookup variants |

### Integration

| Hook / endpoint | Purpose |
|-----------------|---------|
| `woocommerce_locate_template` | Overrides `myaccount/form-lost-password.php` when enabled |
| `lostpassword_post` | Blocks email reset form submission |
| `send_retrieve_password_email` | Disables reset emails |
| `template_redirect` | Blocks email reset URL flows (`reset-link-sent`, `show-reset-form`, etc.) |
| `wc_sms_send_forgot_password_otp` | AJAX — send OTP |
| `wc_sms_verify_forgot_password_otp` | AJAX — verify OTP |
| `wc_sms_resend_forgot_password_otp` | AJAX — resend OTP |
| `wc_sms_reset_forgot_password` | AJAX — update password |

### Session keys

| Key | Purpose |
|-----|---------|
| `wc_sms_forgot_password_user_id` | Verified user ID after OTP |
| `wc_sms_forgot_password_mobile` | Verified mobile after OTP |

### Files

- `public/class-woocoomerce-sms-template-loader.php`
- `public/class-woocoomerce-sms-forgot-password.php`
- `public/class-woocoomerce-sms-phone.php`
- `public/templates/woocommerce/myaccount/form-lost-password.php`
- `public/partials/woocoomerce-sms-forgot-password-form.php`
- `public/partials/woocoomerce-sms-phone-field.php`
- `public/js/woocoomerce-sms-forgot-password.js`
- `public/js/woocoomerce-sms-phone.js`
- `public/css/woocoomerce-sms-auth.css`

---

## Template loader

**Class:** `public/class-woocoomerce-sms-template-loader.php`

- Registers `woocommerce_locate_template` on every request.
- Each template override is applied only when its feature setting is enabled:
  - `myaccount/form-login.php` — when `wc_sms_enable_mobile_login` = `yes`
  - `myaccount/form-lost-password.php` — when `wc_sms_enable_forgot_password_otp` = `yes`
- When features are disabled, WooCommerce default templates are used unchanged.
- Works with default WooCommerce templates, child themes, and theme overrides (plugin templates are used only when no theme override exists).

---

## OTP engine

**Class:** `includes/class-woocoomerce-sms-otp.php`

| Capability | Implementation |
|------------|----------------|
| OTP generation | `random_int()` (fallback: `wp_rand()`) |
| OTP storage | Hashed with `wp_hash_password()` in `{prefix}sms_otp` table |
| OTP verification | `wp_check_password()` + legacy plaintext fallback for old rows |
| Mobile normalization | `normalize_mobile()` — digits + optional leading `+` (E.164) |
| Mobile lookup | `get_user_by_mobile()` — searches `user_login`, `mobile_number`, `billing_phone` via `get_mobile_lookup_variants()` |
| Expiry | Configurable (`wc_sms_otp_expiry`, default 300 seconds) |
| Resend cooldown | Configurable (`wc_sms_otp_resend_timer`, default 60 seconds) |
| Max attempts | Configurable (`wc_sms_otp_max_attempts`, default 5) |
| Invalid after use | `verified = 1` on success; previous OTPs invalidated on resend |
| Cleanup | Daily cron `wc_sms_cleanup_expired_otps` |

### OTP purposes

| Constant | Used for |
|----------|----------|
| `PURPOSE_LOGIN` | My Account login |
| `PURPOSE_FORGOT_PASSWORD` | Lost password |

### Database table `{prefix}sms_otp`

| Column | Type | Notes |
|--------|------|-------|
| `id` | bigint | Primary key |
| `user_id` | bigint | Default `0` |
| `mobile` | varchar(20) | Normalized mobile (E.164 preferred) |
| `otp` | varchar(255) | Hashed OTP |
| `purpose` | varchar(50) | `login`, `forgot_password` |
| `attempts` | int | Failed verify count |
| `verified` | tinyint | `0` = active, `1` = used/invalidated |
| `expires_at` | datetime | UTC |
| `created_at` | datetime | UTC |

**DB version option:** `wc_sms_otp_db_version` = `1.1.0`

### User meta keys

| Meta key | Purpose |
|----------|---------|
| `mobile_number` | Primary mobile identifier (E.164) |
| `mobile_verified` | `0` or `1` |
| `billing_phone` | Synced from mobile on register/verify (E.164) |

---

## Admin settings

### Authentication tab (`?tab=auth_settings`)

**Settings group:** `sms-auth-settings-group`

#### General

| Option | Default | Description |
|--------|---------|-------------|
| `wc_sms_enable_mobile_login` | `no` | Mobile OTP login + extended registration |
| `wc_sms_enable_forgot_password_otp` | `no` | SMS OTP password reset |

#### OTP configuration

| Option | Default | Description |
|--------|---------|-------------|
| `wc_sms_otp_length` | `6` | OTP digit count (4–10) |
| `wc_sms_otp_expiry` | `300` | Validity in seconds |
| `wc_sms_otp_resend_timer` | `60` | Minimum seconds before resend |
| `wc_sms_otp_max_attempts` | `5` | Max failed verification attempts |

All auth options use WordPress `register_setting()` with `sanitize_callback` handlers.

Compatibility notices (if any) are shown via `Woocoomerce_Sms_WooCommerce_Compatibility::get_admin_compatibility_notices()` — see [Admin compatibility notices](#admin-compatibility-notices).

### SMS templates (SMS Settings tab)

OTP templates are configured under **WooCommerce SMS → SMS Settings** (not duplicated in the Authentication tab).

| Option | Default |
|--------|---------|
| `login_otp_sms_template` | `Your login OTP is {otp}. Valid for 5 minutes.` |
| `forgot_password_otp_sms_template` | `Your password reset OTP is {otp}. Valid for 5 minutes.` |

**Placeholders:** `{otp}`, `{site_title}`

---

## AJAX endpoints

All OTP AJAX actions support logged-in and guest (`wp_ajax_nopriv_*`) requests. Each uses a dedicated nonce.

| Action | Nonce | Feature |
|--------|-------|---------|
| `wc_sms_send_login_otp` | `wc_sms_auth_nonce` | Login |
| `wc_sms_verify_login_otp` | `wc_sms_auth_nonce` | Login |
| `wc_sms_resend_login_otp` | `wc_sms_auth_nonce` | Login |
| `wc_sms_send_forgot_password_otp` | `wc_sms_forgot_password_nonce` | Forgot password |
| `wc_sms_verify_forgot_password_otp` | `wc_sms_forgot_password_nonce` | Forgot password |
| `wc_sms_resend_forgot_password_otp` | `wc_sms_forgot_password_nonce` | Forgot password |
| `wc_sms_reset_forgot_password` | `wc_sms_forgot_password_nonce` | Forgot password |

SMS sending in all flows calls `Woocoomerce_Sms_Services_Public::send_sms_message()`.

---

## Security

| Area | Implementation |
|------|----------------|
| AJAX | `check_ajax_referer()` on every OTP endpoint |
| SQL | `$wpdb->prepare()` for OTP queries |
| Input | `sanitize_text_field()`, `normalize_mobile()` (E.164), `sanitize_email()` |
| Mobile lookup | `get_mobile_lookup_variants()` for backward-compatible user search |
| Output | `esc_html()`, `esc_attr()`, `esc_url()` in templates |
| OTP at rest | `wp_hash_password()` |
| Brute force | Max attempts + resend cooldown |
| Password reset | WooCommerce session validated (`reset_mobiles_match()`) before `wp_set_password()` |
| Login block | `woocommerce_process_login_errors` + `authenticate` filter on My Account |
| Admin | `manage_options` capability on settings pages |

---

## Frontend UX

- WooCommerce-native form styling (`woocommerce-Input`, `woocommerce-Button`, `woocommerce-message`, `woocommerce-error`).
- **International phone fields** — intl-tel-input country dropdown with flags + separate dial code; full-width responsive layout.
- Template overrides replace login and lost-password forms (no CSS `display:none` hacks to hide default forms).
- Multi-step OTP flows use the HTML `hidden` attribute for step visibility.
- Loading state on buttons (`wc-sms-btn-loading` spinner + `aria-busy`).
- Success/error messages with `role="alert"` and `aria-live="polite"`.
- Resend countdown timer on all OTP flows.
- Buttons disabled while AJAX requests are in progress.
- Client-side phone validation via `wcSmsPhone.isValid()` before Send OTP (login and forgot password).
- Forgot-password page dequeues WooCommerce `wc-lost-password` script.
- Forgot-password assets enqueue at priority `20` (after WooCommerce scripts).
- Responsive layout; works on mobile, tablet, and desktop.

---

## Authentication file inventory

### PHP classes

| File | Purpose |
|------|---------|
| `includes/class-woocoomerce-sms-woocommerce-compatibility.php` | WooCommerce version/feature detection, session helpers, admin notices |
| `includes/class-woocoomerce-sms-otp.php` | OTP engine, `normalize_mobile()`, `get_mobile_lookup_variants()`, database |
| `public/class-woocoomerce-sms-phone.php` | intl-tel-input assets + shared phone field renderer |
| `public/class-woocoomerce-sms-template-loader.php` | WooCommerce template overrides |
| `public/class-woocoomerce-sms-auth.php` | Login |
| `public/class-woocoomerce-sms-registration.php` | Registration |
| `public/class-woocoomerce-sms-forgot-password.php` | Forgot password |

### WooCommerce templates

| File | Purpose |
|------|---------|
| `public/templates/woocommerce/myaccount/form-login.php` | Mobile OTP login + native register column |
| `public/templates/woocommerce/myaccount/form-lost-password.php` | SMS OTP password reset |

### Partials

| File | Purpose |
|------|---------|
| `public/partials/woocoomerce-sms-login-form.php` | Login UI |
| `public/partials/woocoomerce-sms-phone-field.php` | Shared intl-tel-input field markup |
| `public/partials/woocoomerce-sms-forgot-password-form.php` | Forgot password UI |

### Assets

| File | Purpose |
|------|---------|
| `public/css/woocoomerce-sms-auth.css` | Auth + forgot password + intl-tel-input styles |
| `public/js/woocoomerce-sms-phone.js` | intl-tel-input init, E.164 helpers (`wcSmsPhone`) |
| `public/js/woocoomerce-sms-auth.js` | Login AJAX |
| `public/js/woocoomerce-sms-forgot-password.js` | Forgot password AJAX |
| `public/vendor/intl-tel-input/` | Bundled intl-tel-input library (v23.9.3) |
| `admin/views/partials-html-auth-settings.php` | Authentication admin settings |

### Loader / lifecycle

| File | Purpose |
|------|---------|
| `includes/class-woocoomerce-sms-services.php` | Loads auth + compatibility classes, OTP table upgrade, cleanup cron |
| `includes/class-woocoomerce-sms-services-activator.php` | Creates OTP table and default options on activation |
| `includes/class-woocoomerce-sms-services-deactivator.php` | Clears OTP cleanup cron on deactivation |
| `woocoomerce-sms-services.php` | Plugin bootstrap on `plugins_loaded:20`; HPOS + Blocks compatibility; `Requires Plugins: woocommerce` |

---

## Testing checklist

| Test | Expected result |
|------|-----------------|
| Plugin activates (WC 6.0+) | No PHP errors; `{prefix}sms_otp` table exists |
| Plugin without WooCommerce | Admin notice: WooCommerce required |
| Login | Country code + mobile → OTP → logged in; E.164 sent in AJAX; no username/password fields |
| Login password bypass | Username/password submission blocked on My Account |
| Registration | Name + Email + Mobile (intl-tel-input) + Password → account created with E.164 `user_login` / `mobile_number`, `mobile_verified = 0` |
| Forgot password | Mobile (intl-tel-input) → OTP → new password → **Update Password** succeeds → logged in |
| Legacy mobile users | Users stored without country code still found via lookup variants |
| Features disabled | WooCommerce default login/register/lost-password behaviour |
| Order SMS | Status change still sends SMS (unchanged) |
| Test SMS | Admin test form still works (unchanged) |
| Settings | All admin tabs save correctly |

---

## Production status

| Item | Status |
|------|--------|
| Matches client-approved OTP authentication requirements | Yes |
| Native WooCommerce integration (no shortcodes/pages required) | Yes |
| WooCommerce 6.0+ supported (including 10.9.x) | Yes |
| International phone input (intl-tel-input) | Yes (login, register, forgot password) |
| E.164 mobile storage + legacy lookup | Yes |
| Forgot-password session persistence (guest AJAX) | Yes |
| HPOS + `cart_checkout_blocks` compatibility declared | Yes |
| Child theme / template override compatible | Yes |
| Existing SMS functionality preserved | Yes |
| PHP syntax valid | Yes |
| Live SMS gateway test | Required before go-live |
