# UPLOAD LIST — SMS OTP for Registration & Forgot Password

Mobile OTP on sign-up, mobile OTP **or** the old email link on forgot-password (you
choose which are open), and a congratulations SMS carrying the new member's User ID.

Built on your HSPSMS account and tested end to end against a copy of your own
database — the whole journey was walked through, including the ways it can go wrong.

---

## 1. NEW FILES — upload these

| File | What it is |
|---|---|
| `smsmigration.php` | **root** — the database migration. Run once, then delete. |
| `includes/sms.php` | The engine: the HSPSMS caller, the one-time codes, the templates, the log. |
| `admin/sms_settings.php` | Admin → Website Settings → **SMS & OTP** — everything on one screen. |

## 2. MODIFIED FILES — replace these

| File | What changed |
|---|---|
| `register.php` | Now three screens in one file: the form, the code, and a congratulations page with the User ID. The account is only created once the code is right. |
| `forgot-password.php` | Two tabs — **Mobile OTP** and **Email link** — each one switchable by you. The old email flow is untouched underneath. |
| `reset-password.php` | Accepts both routes: the email link as before, and a confirmed mobile code. |
| `admin/includes/sidebar.php` | New entry: **SMS & OTP**, under Website Settings. |
| `includes/acl.php` | Three new permission points so a role can be given the SMS screen on its own. |
| `includes/activity.php` | The audit trail names the new page properly. |

Your old `register.php`, `forgot-password.php` and `reset-password.php` are kept as
`.bak` files in the `neo/_to_delete/` folder if you ever want to look back.

---

## 3. HOW TO DEPLOY

1. Upload the three new files and the six modified ones.
2. Open **`https://wbmcz.online/smsmigration.php`** — it should say *Migration completed successfully*.
3. **Delete `smsmigration.php` from the server.**
4. Open **Admin → Website Settings → SMS & OTP** and do the two things below.

### The two things only you can do

**a) The Sender ID.** In your HSPSMS panel go to **Setting → Sender ID** and copy your
DLT-approved 6-character header. Paste it into the Sender ID box and save.
Until this is filled in, nothing can be texted — the site stays in test mode instead.

**b) The three templates.** India requires the wording to match a DLT-approved template
exactly, or the operator throws the message away. Paste each approved text into its box.
Only the pieces in `{braces}` are swapped out — leave the rest word for word.

Then untick **Test mode**, and press **Send a test SMS** to your own number.
If it arrives, you are live.

---

## 4. DATABASE CHANGES

### New tables

| Table | Holds |
|---|---|
| `otp_codes` | The codes. Hashed — never stored in plain — with an expiry, a try counter, and whatever the form was carrying until the code is confirmed. |
| `sms_log` | Every message that left the site and every one that failed, with the gateway's own reply so a rejection can be read back. |

### Settings added (20, all editable on the SMS & OTP screen)

Your API key `fc7e7926-…-6a06195b0bc1` and username `esrafilmilla455` are written in for
you. `users` also gains an index on `mobile` so the OTP lookups stay quick. **No existing
setting is overwritten** — the migration only adds what is missing.

---

## 5. HOW REGISTRATION WORKS NOW

1. The member fills the form exactly as before.
2. A code goes to their mobile. **No account exists yet** — everything they typed waits
   with the code, the password already hashed.
3. They type the six digits. Boxes advance on their own, a pasted code fills all of them,
   and a full code submits itself.
4. The account is created, and a congratulations page shows their **User ID** — big, in a
   card, with a note that it doubles as their referral code.
5. The congratulations SMS goes out at the same moment, carrying that same User ID.

> Congratulations Rahul Das! Your WBMCZ account is ready. User ID: WBMCZ1338.
> Referral code: WBMCZ1338. Login at https://wbmcz.online

If a mistake sends them back to the form, everything they typed is still there —
state and district included. That was not the case before.

Turn **OTP on registration** off and the page behaves exactly the way it always did,
congratulations screen and welcome SMS included.

## 6. HOW FORGOT PASSWORD WORKS NOW

Two tabs, and you decide which exist:

* **Mobile OTP** — they type their registered number, confirm the code, and land straight
  on the new-password screen with a green *Mobile number confirmed* badge. That pass
  lapses after fifteen minutes.
* **Email link** — the reset email, exactly as before.

Turn one off and its tab disappears; turn both off and the page says so politely and
points them at support.

## 7. HOW A CODE IS KEPT SAFE

* The code is **hashed before it is stored** — nobody with database access can read it.
* It **dies after 10 minutes** (your number, adjustable).
* **Five wrong tries** and that code is burnt. The screen counts down: *"4 tries left."*
* A new code cannot be asked for **within 60 seconds**, and the button shows the wait.
* **10 codes per number per day**, so nobody can burn through your credit.
* A code is thrown away the moment it has done its job, so it can never be replayed.

Every one of those numbers is yours to change on the settings screen.

## 8. TEST MODE

While Test mode is on, nothing is texted — the code appears on the screen in an amber
box instead. So the whole flow works today, before the DLT header comes through. It is
switched **on** by the migration on purpose. Turn it off once your test SMS arrives.

## 9. WHAT WAS FOUND AND FIXED DURING TESTING

Worth knowing, because it would have bitten you on the live server:

**PHP and MySQL keep different clocks.** Your `config.php` sets PHP to Asia/Kolkata, while
most hosting databases run on UTC — five and a half hours apart. Any code comparing a
database timestamp against PHP's clock silently gets the wrong answer. On the test run the
resend cooldown never fired and the countdown showed zero seconds. Every such comparison
now happens **inside MySQL**, so it is right whatever the two servers are set to.

Also confirmed during testing: an account is never created before the code is confirmed;
a duplicate mobile or email is refused; a gateway failure shows a clear message, writes the
reason to the log, and leaves no half-made account behind.

## 10. WHAT TO CHECK AFTER UPLOADING

1. **Admin → Website Settings → SMS & OTP** — the amber banner should name the Sender ID
   as the one thing still missing. Fill it in, paste your templates, save.
2. Press **Send a test SMS** to your own number. If it fails, the gateway's own words are
   shown, and the same reason lands in *Messages that went out*.
3. Untick **Test mode**, save, and register a throwaway account on your own phone. The code
   should arrive, and the congratulations SMS right after it with the User ID.
4. Try **Forgot password → Mobile OTP** with that same number.
5. Press **Check credit** in the header to confirm the account is talking to HSPSMS.

---

# UPDATE — 5 Sep 2026: why nothing was arriving, and the fix

Two separate things were wrong. Both are fixed now, and both live in files you
already have — no migration, no new table.

## 1. A delivered message was being read as a failure

HSPSMS does not answer in one fixed shape. Alongside the old

    {"ErrorCode":"000","ErrorMessage":"Success","MessageData":[{"MessageId":"456"}]}

it also answers

    [{"responseCode":"Message SuccessFully Submitted"},{"msgid":"32014584"}]

The old reader only knew the first shape, so a message that had gone out came
back as *failed* and the raw JSON was shown to the member in a red box.

`includes/sms.php` now flattens the reply however deeply it is nested and reads
it key by key — `msgid`, `messageid`, `jobid`, `responsecode`, `errorcode`,
`status`. Six real replies were tested against it: both success shapes pass,
and *Invalid Username*, *Sender Id Not Found*, *Insufficient Credit* and
*Template Mismatch* all still fail, now with the gateway's own sentence shown
instead of raw JSON.

## 2. The wording was not a registered DLT template

The header was fine. The wording was not. India scrubs every transactional text
against the DLT registry: an unregistered body is accepted by the gateway,
**billed**, and then dropped by the operator — it comes back as
*expired / undelivered*, never as a rejection, which is what made it look like a
gateway problem.

The body approved under this account's header is

    {#var#} is the OTP for {#var#}. Please do not share this OTP with anyone. This SMS has been sent from GuestRAR.

so the site now sends exactly that, with `{otp}` and `{company}` in the two
`{#var#}` slots and every other word untouched. **Admin → Website Settings →
SMS & OTP → “The wording the operator accepts” → Use the approved wording** sets
both OTP templates in one press, and switches the welcome SMS off because it has
no approved template of its own.

## Also on that screen now

* **Did it actually reach the phone?** — paste a message ID, get the operator's
  own delivery status. `DELIVRD` / `UNDELIV` / `EXPIRED` / `REJECTD` / `DND` are
  explained underneath, so a future failure is one look instead of a guess.
* `sms_dlr()` in `includes/sms.php` is what it calls.

## Files to upload

| File | Why |
|---|---|
| `includes/sms.php` | the reply reader, and the new delivery-report call |
| `admin/sms_settings.php` | the approved-wording button and the delivery check |

The one-time `smsconfig.php` has been moved to `_to_delete/` — everything it did
now lives on the admin screen, behind the admin login and the `website.sms`
permission. Do not upload it.

## Still open

`GuestRAR` is the reseller's brand, and members will read it in the message. To
send OTPs under your own name you need your own DLT entity, your own header, and
your own approved templates. Nothing in the code has to change for that — only
the Sender ID box and the two template boxes.
