=== WhatsOS OTP Verification ===
Contributors: whatsos
Requires at least: 6.4
Requires PHP: 8.0
Stable tag: 1.0.0
License: GPL-2.0-or-later
License URI: https://www.gnu.org/licenses/gpl-2.0.html

WhatsApp phone verification for WordPress, using the WhatsOS OTP API.

== Installation ==
1. Download https://whatsos.com/otp/downloads/whatsos-otp-wordpress-1.0.0.zip
2. WordPress admin > Plugins > Add New > Upload Plugin. Upload the ZIP, install and activate.
3. In the WhatsOS OTP workspace (https://whatsos.com/otp), create a TEST project and API key with otp:send, otp:verify and otp:cancel scopes.
4. WordPress > Settings > WhatsOS OTP: paste the key, enable forms, set purpose and template language to match the project. Default purpose: phone_verification; language: en_US.
5. Add [whatsos_otp] in a Shortcode block, Elementor shortcode widget or another builder's shortcode element. Publish the page.
6. Request a code with a fictional E.164 phone number (e.g. +12025550123). Sandbox delivery is simulated. Use code 123456.
7. For live delivery, configure your WhatsOS live project, annual licence, connected WhatsApp sender and approved authentication template. Use an HTTPS WordPress site, replace the test key with a scoped live key and perform a controlled verification requested by its recipient.

PHP OpenSSL and outbound HTTPS access to whatsos.com are required. Keep WordPress salts stable. Rotating salts invalidates encrypted keys, sessions and proofs; save your key again afterwards. Alternatively set WHATSOS_OTP_API_KEY in wp-config.php from your server's secret manager. Never put credentials in page content, source control, browser code or AI prompts.

== Included ==
Settings, shortcode form, send/check/resend/cancel, masked recipient display, six-digit input, resend cooldown, encrypted API key storage, server-side challenge binding, same-origin/CSRF protection, per-IP request limits and one-time proof consumption.

The plugin verifies phone ownership. It does not automatically log in users, create accounts or modify WooCommerce checkout. A developer must connect the server-side result to that workflow. Only one active challenge per browser session is supported; multiple forms share the session. The site's main origin must match the form origin. Embedded cross-site iframe forms are not supported.

== Connect a protected workflow ==
After the form reports success, your own trusted server handler must validate its authentication/authorization, nonce or action-session CSRF token, and the business input. Read the expected E.164 number from the action being approved; do not accept an unrelated browser-supplied verification ID. Then call:

    $proof = whatsos_otp_consume($expected_phone, 'phone_verification');
    if (is_wp_error($proof)) {
        // Reject the protected action. Do not mark the phone verified.
        return $proof;
    }
    // Complete only the matching action and record its unique completion ID.

Proofs expire five minutes after a successful check. Consumption is atomic and one-time. Wrong phone, purpose, expired proof, another browser session, key rotation and previously consumed proofs are rejected. Default consumption accepts LIVE verification only. Sandbox automation may explicitly pass true as the third argument, but never enable this in a real login, checkout or production handler.

Your business handler must deduplicate its own action and handle failure between proof consumption and persistence; this function does not provide a transaction spanning your business database. A verified number alone is not account authorization. Do not map an arbitrary submitted phone to an existing administrator account.

The optional PHP action whatsos_otp_verified receives ($phone, $purpose) only for live verification. Use it for an immediate, explicitly bound server workflow, and consume the proof before approving an action. The separate whatsos_otp_test_verified action has no arguments. Hooks are synchronous notifications, not a durable queue. Do not throw exceptions from hook callbacks. The browser event whatsos:verified is for UI updates only and is never authentication proof.

== Delivery, errors and retries ==
Sending queues a request; it does not prove WhatsApp delivery. Codes normally expire after five minutes. Resend is available after at least 30 seconds and replaces the old verification ID. The plugin retains the original idempotency key and payload after uncertain requests. Retry that same action before changing numbers or starting another operation. After a definitive rate limit, wait before retrying. Repeated invalid codes can lock the upstream challenge.

If a refresh displays an active challenge, enter its code or choose another number. Check that browser cookies are enabled. Exclude the /whatsos-otp/v1 REST routes from page caching and ensure your security plugin permits these same-origin POST requests. A cached shortcode page is supported because credentials and session tokens are loaded through an uncached bootstrap request.

Live mode requires WordPress is_ssl() to recognize HTTPS. Configure trusted reverse proxy HTTPS handling at the hosting layer. Rate limits use REMOTE_ADDR and do not trust arbitrary X-Forwarded-For values; sites behind a proxy should configure the trusted real client IP at their server. Shared IPs can share limits.

== External service and privacy ==
When a visitor requests or verifies a code, this plugin sends their phone number, purpose, language, opaque action reference and submitted verification code over HTTPS to https://whatsos.com/api/v1/otp. The site owner must have a WhatsOS account and configure the key. Live delivery uses the WhatsApp sender configured in that project. Applicable WhatsOS licence and Meta message charges are described at https://whatsos.com/whatsapp-otp#plans.

Service terms: https://whatsos.com/terms-of-service
Privacy policy: https://whatsos.com/privacy-policy

The plugin sets a necessary HttpOnly, SameSite=Strict session cookie lasting 30 minutes (Secure on HTTPS). It stores a salted hash of the client IP for request limits, an encrypted phone number, hashed phone/code comparisons, challenge IDs and short-lived verification state in WordPress transients. Active request state lasts up to 15 minutes; completed proof state up to 10 minutes, with proof validity limited to five minutes. Transient cleanup depends on WordPress/cache expiration; expired database rows can remain until cleanup runs. It creates no permanent customer table or OTP logs. Hooks added by a site may store additional data; document those separately. API key encryption protects stored values but does not protect against a compromised WordPress server.

Uninstall deletes the plugin's settings, local transient records and lock records. It does not revoke your WhatsOS key or cancel upstream challenges. Revoke an unused key from the WhatsOS workspace.

== Reference ==
Integration guide: https://whatsos.com/otp/integrations.html
API guide: https://whatsos.com/otp/guide.html
OpenAPI: https://whatsos.com/otp/openapi.json
WordPress REST routes: https://developer.wordpress.org/reference/functions/register_rest_route/
WordPress transient storage: https://developer.wordpress.org/apis/transients/

== Changelog ==
= 1.0.0 =
Initial shortcode, settings and server-side verification integration.
