dev1/whatspass 问题修复 & 功能扩展

解决BUG、新增功能、兼容多环境部署,快速响应你的开发需求

邮箱:yvsm@zunyunkeji.com | QQ:316430983 | 微信:yvsm316

dev1/whatspass

最新稳定版本:v1.0.0

Composer 安装命令:

composer require dev1/whatspass

包简介

Send OTP authentication messages via WhatsApp using Meta's official Cloud API. Supports Laravel and Symfony.

README 文档

README

CI Latest Version on Packagist PHP Version License

Send OTP authentication codes via WhatsApp using Meta's official Cloud API.

Whatspass is a framework-agnostic PHP library with first-class support for Laravel and Symfony. It talks directly to Meta's WhatsApp Business Cloud API — no third-party intermediaries, no extra billing layers.

Requirements

Requirement Version
PHP ≥ 8.2
Laravel (optional) 11 or 12
Symfony (optional) 6.4 or 7.x

Installation

composer require dev1/whatspass

Before You Start — Meta Setup

You need a Meta for Developers account with a WhatsApp Business App. Here is the one-time setup:

  1. Go to developers.facebook.com and create an App (type: Business).
  2. Add the WhatsApp product to your app.
  3. In WhatsApp → API Setup, copy your:
    • Phone Number ID — the numeric ID that identifies the number you send from.
    • Temporary access token — or generate a permanent System User token from the Business Manager.
  4. Create and get a Message Template approved in the Meta Business Manager. For OTP delivery, a simple body like "Your verification code is {{1}}." is all you need.

Note: Template messages work at any time. Free-form text messages (type: text) only work within a 24-hour customer-care window (i.e., the recipient must have messaged you first).

Laravel

1. Publish the config

php artisan vendor:publish --tag=whatspass-config

This creates config/whatspass.php.

2. Set environment variables

Copy .env.example entries to your .env file and fill in your credentials:

# Required
WHATSPASS_PHONE_NUMBER_ID=123456789012345
WHATSPASS_ACCESS_TOKEN=your_access_token_here

# Optional (defaults shown)
WHATSPASS_API_VERSION=v19.0
WHATSPASS_TEMPLATE_NAME=otp_authentication
WHATSPASS_LANGUAGE_CODE=en_US
WHATSPASS_OTP_LENGTH=6
WHATSPASS_OTP_EXPIRY=300
WHATSPASS_ALPHANUMERIC_OTP=false

3. Send an OTP

Using the Facade:

use Dev1\Whatspass\Laravel\Facades\Whatspass;

// Generate a code and send it in one step
$result = Whatspass::generateAndSend('+15551234567');

$otp      = $result['otp'];       // "483920"  — store this to verify later
$response = $result['response'];  // raw Meta API response

Using dependency injection:

use Dev1\Whatspass\Contracts\WhatspassServiceInterface;

class AuthController extends Controller
{
    public function __construct(
        private readonly WhatspassServiceInterface $whatspass,
    ) {}

    public function sendOtp(Request $request): JsonResponse
    {
        $phone = $request->input('phone'); // e.g. "+15551234567"

        $result = $this->whatspass->generateAndSend($phone);

        // Store $result['otp'] in your session/cache to verify later
        session(['otp' => $result['otp'], 'otp_phone' => $phone]);

        return response()->json(['message' => 'OTP sent.']);
    }
}

Configuration reference

// config/whatspass.php
return [
    'phone_number_id'       => env('WHATSPASS_PHONE_NUMBER_ID'),
    'access_token'          => env('WHATSPASS_ACCESS_TOKEN'),
    'api_version'           => env('WHATSPASS_API_VERSION', 'v19.0'),
    'base_url'              => env('WHATSPASS_BASE_URL', 'https://graph.facebook.com'),
    'default_template_name' => env('WHATSPASS_TEMPLATE_NAME', 'otp_authentication'),
    'default_language_code' => env('WHATSPASS_LANGUAGE_CODE', 'en_US'),
    'otp_length'            => (int) env('WHATSPASS_OTP_LENGTH', 6),     // 4–12 characters
    'otp_expiry'            => (int) env('WHATSPASS_OTP_EXPIRY', 300),   // seconds (≥ 60)
    'alphanumeric_otp'      => (bool) env('WHATSPASS_ALPHANUMERIC_OTP', false),
];

Rate limiting (Laravel)

By default the library uses a no-op rate limiter. To enforce per-phone-number limits in production, bind your own implementation to RateLimiterInterface:

// app/Providers/AppServiceProvider.php
use Dev1\Whatspass\Contracts\RateLimiterInterface;
use App\Services\RedisWhatspassRateLimiter;

public function register(): void
{
    $this->app->bind(RateLimiterInterface::class, RedisWhatspassRateLimiter::class);
}

Example implementation using Laravel Cache:

use Dev1\Whatspass\Contracts\RateLimiterInterface;
use Dev1\Whatspass\Exceptions\RateLimitExceededException;
use Illuminate\Support\Facades\Cache;

class CacheRateLimiter implements RateLimiterInterface
{
    public function __construct(
        private readonly int $maxAttempts = 3,
        private readonly int $decaySeconds = 60,
    ) {}

    public function attempt(string $phoneNumber): void
    {
        $key   = 'whatspass:' . hash('sha256', $phoneNumber);
        $hits  = (int) Cache::get($key, 0);

        if ($hits >= $this->maxAttempts) {
            throw new RateLimitExceededException($phoneNumber);
        }

        Cache::put($key, $hits + 1, $this->decaySeconds);
    }
}

Symfony

1. Register the bundle

// config/bundles.php
return [
    // ...
    Dev1\Whatspass\Symfony\WhatspassBundle::class => ['all' => true],
];

2. Add configuration

# config/packages/whatspass.yaml
whatspass:
    phone_number_id:       '%env(WHATSPASS_PHONE_NUMBER_ID)%'
    access_token:          '%env(WHATSPASS_ACCESS_TOKEN)%'
    api_version:           'v19.0'
    default_template_name: 'otp_authentication'
    default_language_code: 'en_US'
    otp_length:            6
    otp_expiry:            300
    alphanumeric_otp:      false

Set the required values in your .env file:

WHATSPASS_PHONE_NUMBER_ID=123456789012345
WHATSPASS_ACCESS_TOKEN=your_access_token_here

3. Send an OTP

use Dev1\Whatspass\Contracts\WhatspassServiceInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\JsonResponse;

class AuthController extends AbstractController
{
    public function __construct(
        private readonly WhatspassServiceInterface $whatspass,
    ) {}

    public function sendOtp(string $phone): JsonResponse
    {
        $result = $this->whatspass->generateAndSend($phone);

        // Store $result['otp'] in your session/cache for verification
        return new JsonResponse(['message' => 'OTP sent.']);
    }
}

Rate limiting (Symfony)

Register your implementation as an alias for RateLimiterInterface:

# config/services.yaml
services:
    App\Service\WhatspassRateLimiter:
        arguments:
            $maxAttempts: 3
            $decaySeconds: 60

    Dev1\Whatspass\Contracts\RateLimiterInterface:
        alias: App\Service\WhatspassRateLimiter

Framework-agnostic usage

No framework? No problem. Instantiate everything manually:

use Dev1\Whatspass\OtpGenerator;
use Dev1\Whatspass\WhatspassClient;
use Dev1\Whatspass\WhatspassConfig;
use Dev1\Whatspass\WhatspassService;

$config = WhatspassConfig::fromArray([
    'phone_number_id' => getenv('WHATSPASS_PHONE_NUMBER_ID'),
    'access_token'    => getenv('WHATSPASS_ACCESS_TOKEN'),
    'otp_length'      => 6,
]);

$service = new WhatspassService(
    config:    $config,
    client:    new WhatspassClient($config),
    generator: new OtpGenerator(),
);

// Generate and send
$result = $service->generateAndSend('+15551234567');
echo $result['otp']; // "749201"

// Or send a code you generated yourself
$otp = $service->generateOtp();
$service->sendOtp('+15551234567', $otp);

API Reference

WhatspassService

All methods are available through the facade, dependency injection, or direct instantiation.

generateOtp(?int $length, ?bool $alphanumeric): string

Generate a cryptographically secure OTP code.

$otp = $service->generateOtp();                   // "483920"   (6-digit numeric, from config)
$otp = $service->generateOtp(length: 8);          // "20938471"
$otp = $service->generateOtp(alphanumeric: true); // "4aB9Qz"

sendOtp(string $phone, string $otp, array $options): array

Send an existing OTP to a phone number. Returns the raw Meta API response.

// Template message (default)
$response = $service->sendOtp('+15551234567', '483920');

// Free-form text message
$response = $service->sendOtp('+15551234567', '483920', [
    'type'           => 'text',
    'custom_message' => 'Your Acme code is {otp}. Expires in 5 minutes.',
]);

// Override template per message
$response = $service->sendOtp('+15551234567', '483920', [
    'template_name' => 'acme_otp_es',
    'language_code' => 'es_ES',
]);
Option Type Default Description
type string 'template' 'template' or 'text'
template_name string config value Override the Meta template name
language_code string config value BCP-47 language code (e.g. pt_BR)
custom_message string Text body for type: text. Use {otp} as placeholder

generateAndSend(string $phone, array $options): array

Generate a code and send it in one step.

$result = $service->generateAndSend('+15551234567');

$result['otp'];      // "483920"  — persist this to verify the user later
$result['response']; // Meta API response array

Accepts the same $options as sendOtp(), plus:

Option Type Default Description
otp_length int config value Override OTP length for this call
alphanumeric_otp bool config value Override alphanumeric flag for this call

send(OtpMessage $message): array

Send a manually constructed OtpMessage object directly.

use Dev1\Whatspass\MessageType;
use Dev1\Whatspass\OtpMessage;

$message = new OtpMessage(
    to:            '+15551234567',
    otp:           '839201',
    type:          MessageType::Text,
    customMessage: 'Hi! Your login code is {otp}.',
);

$response = $service->send($message);

Phone Number Format

Phone numbers are automatically normalized to E.164 format. All of these are accepted:

+1 (555) 123-4567  →  +15551234567
+1-555-123-4567    →  +15551234567
15551234567        →  +15551234567

An InvalidPhoneNumberException is thrown if the number cannot be normalized (e.g. too short, longer than 30 characters, starts with 0, or contains letters).

Error Handling

use Dev1\Whatspass\Exceptions\ApiException;
use Dev1\Whatspass\Exceptions\InvalidConfigException;
use Dev1\Whatspass\Exceptions\InvalidPhoneNumberException;
use Dev1\Whatspass\Exceptions\RateLimitExceededException;

try {
    $result = $service->generateAndSend($phone);
} catch (RateLimitExceededException $e) {
    // Too many OTP requests for this phone number
    return response()->json(['message' => 'Too many requests. Please wait and try again.'], 429);
} catch (InvalidPhoneNumberException $e) {
    // The phone number format is invalid
    logger()->warning('Bad phone number', ['error' => $e->getMessage()]);
} catch (ApiException $e) {
    // Meta API returned an error (4xx/5xx) or the connection failed
    logger()->error('WhatsApp API error', ['code' => $e->getCode()]);
} catch (InvalidConfigException $e) {
    // Misconfigured phone_number_id, access_token, otp_length, etc.
}

Exception hierarchy

\RuntimeException
└── WhatspassException
    ├── ApiException              — Meta API / network errors
    └── RateLimitExceededException — Rate limit hit for a phone number

\InvalidArgumentException
├── InvalidConfigException        — Bad configuration values
└── InvalidPhoneNumberException   — Unparseable phone number

Security

The library enforces the following security constraints out of the box:

  • HTTPS onlybase_url must start with https://. Plain HTTP is rejected at config-load time.
  • TLS verification — Guzzle is explicitly configured with verify: true. It cannot be silently disabled.
  • Numeric Phone Number IDphone_number_id must be all digits, matching Meta's format.
  • Phone masking in logs — recipients are logged as +155*****67 to avoid PII leakage.
  • Redacted API errors — only error_code and error_type are logged; full Meta error bodies are never written to logs.
  • Cryptographic OTP generation — all codes are generated with random_int() (CSPRNG).
  • ReDoS guard — phone number inputs longer than 30 characters are rejected before any regex processing.

Logging

WhatspassClient accepts any PSR-3 logger. In Laravel and Symfony it is injected automatically. When provided, it logs:

  • debug — before every request (masked recipient, message type)
  • info — on success (masked recipient, Meta message ID)
  • error — on API errors (HTTP status, error code and type only) and connection failures (exception class only)

Phone numbers are always masked in log output (e.g. +155*****67).

Testing

# Run the test suite
composer test

# Run with code coverage report (requires Xdebug or PCOV)
composer test:coverage

Mocking in your own tests

The library uses Guzzle's MockHandler, making it straightforward to test your own code without hitting the real API:

use Dev1\Whatspass\OtpGenerator;
use Dev1\Whatspass\WhatspassClient;
use Dev1\Whatspass\WhatspassConfig;
use Dev1\Whatspass\WhatspassService;
use GuzzleHttp\Client;
use GuzzleHttp\Handler\MockHandler;
use GuzzleHttp\HandlerStack;
use GuzzleHttp\Psr7\Response;

$mock = new MockHandler([
    new Response(200, [], json_encode([
        'messaging_product' => 'whatsapp',
        'messages' => [['id' => 'wamid.test123']],
    ])),
]);

$httpClient = new Client(['handler' => HandlerStack::create($mock)]);
$config     = WhatspassConfig::fromArray([
    'phone_number_id' => '123456789012345',
    'access_token'    => 'test-token',
]);

$service = new WhatspassService(
    config:    $config,
    client:    new WhatspassClient($config, $httpClient),
    generator: new OtpGenerator(),
);

$result = $service->generateAndSend('+15551234567');
// No real HTTP request is made

CI / CD

The repository ships with two GitHub Actions workflows:

Workflow Trigger What it does
CI (.github/workflows/ci.yml) Push / PR to main, master, develop Runs the test suite on PHP 8.2, 8.3, and 8.4, and enforces ≥ 80% coverage
Release (.github/workflows/release.yml) Push of a v*.*.* tag Runs tests, then creates a GitHub Release with auto-generated notes

Publishing a release

git tag v1.0.0
git push origin v1.0.0

The Release workflow runs the full test suite and creates a GitHub Release automatically.

License

The MIT License (MIT). See LICENSE.md for details.

Made in Mexico with ❤️ by DEV1 Labs, part of DEV1 Softworks.

统计信息

  • 总下载量: 1
  • 月度下载量: 0
  • 日度下载量: 0
  • 收藏数: 0
  • 点击次数: 5
  • 依赖项目数: 0
  • 推荐数: 0

GitHub 信息

  • Stars: 0
  • Watchers: 0
  • Forks: 0
  • 开发语言: PHP

其他信息

  • 授权协议: MIT
  • 更新时间: 2026-03-11

承接程序开发

PHP开发

VUE

Vue开发

前端开发

小程序开发

公众号开发

系统定制

数据库设计

云部署

网站建设

安全加固