3neti / form-handler-otp
OTP verification handler for form flow system
Requires
- php: ^8.2
- 3neti/form-flow: ^1.11
- illuminate/support: ^12.0 || ^13.0
- spatie/laravel-data: ^4.0
Requires (Dev)
- inertiajs/inertia-laravel: ^2.0 || ^3.0
- laravel/pint: ^1.0
- orchestra/testbench: ^10.3 || ^11.0
- pestphp/pest: ^3.8 || ^4.0
- pestphp/pest-plugin-laravel: ^3.2 || ^4.0
Suggests
None
Provides
None
Conflicts
None
Replaces
None
This package is auto-updated.
Last update: 2026-11-11 08:11:15 UTC
README
3neti/form-handler-otp adds mobile one-time-password verification to
3neti/form-flow. It delegates OTP generation, delivery, expiry, attempt
tracking, and verification to a configured Txtcmdr service.
Requirements
- PHP 8.2 or newer
- Laravel 12 or 13
- Form Flow 1.8 or newer
- Inertia Laravel 2 or 3 in applications that render the published Vue page
Installation
composer require 3neti/form-handler-otp php artisan otp-handler:install --no-interaction
The package service provider registers the otp handler automatically. The
install command publishes the package configuration and Vue page.
Configuration
Publish the configuration independently when needed:
php artisan vendor:publish --tag=otp-handler-config
Configure the Txtcmdr endpoint and bearer token in the host environment:
TXTCMDR_API_URL=https://txtcmdr.example TXTCMDR_API_TOKEN= TXTCMDR_CONNECT_TIMEOUT=5 TXTCMDR_TIMEOUT=15 TXTCMDR_VERIFY_SSL=true OTP_HANDLER_DRIVER=txtcmdr OTP_MAX_RESENDS=3 OTP_RESEND_COOLDOWN=30
Do not commit the API token. The handler sends it only as the bearer token for Txtcmdr requests.
Form Flow contract
Add an otp step after a step that collects mobile:
[
'handler' => 'otp',
'config' => [
'purpose' => 'onboarding.account',
'max_resends' => 3,
'resend_cooldown' => 30,
'digits' => 6,
'ui_variant' => 'compact',
],
]
The handler reads the mobile number from the current Form Flow session. It fails before contacting Txtcmdr if no mobile number has been collected.
Rendering the handler has no delivery side effect. The claimant explicitly selects Send verification code. The handler then:
- creates a challenge through
POST /api/v1/otp/challenges; - stores only the returned challenge reference in the session;
- enables verification and the throttled resend control.
On submission, it verifies through
POST /api/v1/otp/challenges/{challenge}/verify. A successful verification
clears the challenge and delivery-control session state, then
returns:
[
'mobile' => '+639171234567',
'verified_at' => '2026-07-31T15:00:00+08:00',
'reference_id' => 'flow-123',
'verification_reference' => '01K...',
'verification_purpose' => 'onboarding.account',
]
The raw code is never returned as Form Flow result data. Hosts must validate the structured proof against the expected purpose and claimant identity before executing a protected workflow.
User interface
The published Vue page supports Form Flow's default, compact, and
immersive UI variants. It uses the shared Form Flow screen and action
components, accepts numeric input, submits the nested data.otp_code payload,
and focuses the code input on entry.
Delivery safeguards
- Verification identifiers are isolated by Form Flow reference in the session.
- Opening or refreshing the page cannot send an SMS.
- Successful codes are one-time because Txtcmdr is the verification authority and local session state is cleared after acceptance.
- Resend count and cooldown are enforced by the server handler, not only by the browser.
- Provider rejection reasons are mapped to sanitized validation messages.
- A missing verification session or mobile number fails closed.
- Hosts should still rate-limit Form Flow endpoints by authenticated principal, session, mobile reference, and source address.
Testing
composer test
composer pint -- --test
composer audit
The package suite fakes HTTP and verifies the request, session, resend, provider-rejection, expiry, and UI render contracts without sending SMS.
License
MIT
YAML presentation integration
The expanded tokens/screens/actions contract requires the companion Form Flow
changes in this development slice; it is not provided by the released v1.10.0
alone. Release the manager before its dependent handler versions. The portable
schema, inheritance and limits are documented in docs/PRESENTATION.md in
3neti/form-flow. x-change owns its Pay Code
profile and domain driver; this package owns the handler interaction.
In an existing complete YAML driver, merge this step configuration:
steps: otp: handler: otp step_name: otp_step title: Verify phone number config: presentation: layout: density: compact actions: placement: viewport_bottom
The driver may also select a registered profile in top-level presentation.
Step settings override driver settings, which override the selected profile
and manager defaults. The handler page uses the shared presentation and action
components; legacy ui_variant remains a fallback without that policy.
The Send Verification Code and Verify OTP states share a step. A step-level
primary_label applies to both states, so leave it unset unless a single label
is accurate for both. Resend/error styling remains component-owned. Delivery,
expiry, retry limits, and verification are handler behavior, not presentation.
After package updates, publish this handler's Vue resources using its installer
and rebuild the standalone host. In x-change hosts, use
php artisan x-change:publish --scope=build --force --verify --no-interaction
and the host build workflow instead. Review intentional host customizations
before forced publication; never maintain fixes in generated host copies.
Verify a fresh fixture-based flow and preview after changes. Active flows retain their frozen presentation. Test initial, processing, error, and completed/review states on mobile; do not collect real identity evidence or send real OTPs merely to verify appearance. Unsupported schema settings must fail validation.