medz/laravel-jpush-notification-channel
A Laravel notification channel for JPush (JiGuang Push). Send push notifications to Android, iOS, and other clients through Laravel's built-in notification system with a simple, fluent API and full support for JPush audience targeting and payload options.
Install and wire it up
composer require medz/laravel-jpush-notification-channel
Laravel 5.5+ auto-discovers the service provider, but on Lumen or with dont-discover you must register Medz\Laravel\Notifications\JPush\JPushServiceProvider in config/app.php (or bootstrap/app.php) and alias the channel if you plan to reference it by short name.
Publish the config and set credentials from the JPush console:
php artisan vendor:publish --provider="Medz\Laravel\Notifications\JPush\JPushServiceProvider"
JPUSH_APP_KEY=xxxxxxxxxxxxxxxxxxxxxxxx
JPUSH_MASTER_SECRET=xxxxxxxxxxxxxxxxxxxxxxxx
Where to look first
vendor/medz/laravel-jpush-notification-channel/src/ — confirm the exact namespace, channel class (JPushChannel), message class (JPushMessage), and whether the message proxies to the underlying jpush/jpush PushPayload. The public API has shifted across releases; read the source before copy-pasting docs.config/jpush.php — check whether it expects app_key/master_secret keys directly or an options array.User model — decide how you'll route. The channel resolves the audience via routeNotificationFor('jpush'), which calls routeNotificationForJpush() on the notifiable. Return a registration ID, an array of IDs, or an alias/tag.First use: a single push
use Illuminate\Notifications\Notification;
use Medz\Laravel\Notifications\JPush\JPushChannel;
use Medz\Laravel\Notifications\JPush\JPushMessage;
class OrderShipped extends Notification
{
public function via($notifiable)
{
return [JPushChannel::class]; // or 'jpush'
}
public function toJPush($notifiable)
{
return (new JPushMessage)
->setPlatform('all')
->setNotification([
'alert' => 'Your order has shipped',
'android' => ['alert' => 'Your order has shipped', 'title' => 'Shipped'],
'ios' => ['alert' => 'Your order has shipped', 'sound' => 'default', 'badge' => 1],
])
->setExtras(['order_id' => 123]);
}
}
$user->notify(new OrderShipped);
If the message class forwards unknown methods to the SDK payload builder, anything in the JPush REST API (setAudience, setOptions, setMessage, addTag, bigPushDuration, …) is reachable through the same fluent object.
Route by alias for logged-in users, registration ID for logged-out ones. Storing a jpush_registration_id per device row is the most reliable route, but alias routing (routeNotificationForJpush() returning ['alias' => (string) $user->id] or the audience set directly on the message) survives app reinstalls and avoids stale IDs. Use registration IDs for targeted, ephemeral pushes (e.g. a chat message to the current device), aliases for user-scoped campaigns.
Multi-channel notifications. Combine the push channel with database/mail in via(). Guard the push so it doesn't fire for users who disabled push:
public function via($notifiable)
{
return array_filter([
$notifiable->push_enabled ? JPushChannel::class : null,
'database',
]);
}
Queue the notification, not the channel. implements ShouldQueue on the notification is the right level — JPush calls are HTTP round-trips and the channel has no internal batching. Set tries/backoff and implement failed() for visibility, since the channel throws on API errors.
Platform-specific payloads. Build android and ios blocks explicitly rather than relying on alert alone; JPush's default behavior across platforms is inconsistent. For Chinese Android OEMs, set third_party_channel / vendor passthrough fields in the android block if you need delivery off the JPush long-connection channel.
Direct channel use outside notifications. For broadcast/admin tooling, resolve the channel from the container and call send() with a notifiable stub, or just use jpush/jpush directly. The channel is a thin adapter — for complex segmentation (addTag, addAllAudience, setSmsg), the underlying PushPayload is often clearer.
Testing. Notification::fake() asserts the notification was sent, not that JPush accepted it, which is what you normally want in tests. For channel-level tests, bind a fake client into the container (the service provider resolves JPush\Client — check the binding in source) and assert on setPlatform/setAudience calls. Never hit the real JPush API in CI.
Config-driven environments. Keep a jpush.php config that reads apns_production from an env var so staging can send to the iOS sandbox APNs while production sends to the real one. Default the message's setOptions(['apns_production' => config('jpush.apns_production')]) in a base notification class rather than per-notification.
composer.json. It targets Laravel 5.5–6.x conventions; on Laravel 9+/10+ you'll likely hit method-signature and type-hint mismatches. For anything long-lived, fork it or skip the wrapper and inject jpush/jpush into a small custom channel — it's ~40 lines of code.JPUSH_APP_KEY/JPUSH_MASTER_SECRET, run php artisan config:cache (and config:clear in dev). A cached empty config surfaces as an auth error from JPush, not a Laravel error.routeNotificationForJpush() returns null or [] and the message has no explicit setAudience, you'll get a JPush validation error. Always set a fallback audience or short-circuit via().APIRequestException, APIConnectionException, and AuthenticationException. Catch them inside queued notifications and log the JPush error.code/error.message; otherwise a single bad registration ID fails the whole job. Registration IDs that are no longer valid come back in the response body under error, not as an exception — inspect the response if you care about cleanup.apns_production => false via setOptions(). Forgetting this on staging produces "push succeeded but never arrived" — the most common JPush debugging dead-end.android.notification.channel_id matching a channel created client-side, notifications silently downgrade (no heads-up, no sound). Set it in the android payload block.via() naming matters. 'jpush' (short name) requires the channel to be registered under that alias; JPushChannel::class always works. Pick one and be consistent — mixed usage causes "class not found" for the alias.JPushMessage instance. It's mutable and fluent; sharing one across notifications leaks payload settings between sends. Build a fresh instance in each toJPush().error.code 1000-series responses. Gate bulk sends behind an explicit admin action and log the response sendno/msg_id for reconciliation.php artisan package:discover doesn't list the package, the extra.laravel.providers key is missing or your composer.json has a stale dont-discover entry. On Lumen, register the provider manually — discovery isn't supported there.(array) $payload or the SDK's getPayload()) before the request when a push silently no-ops. Nine times out of ten the audience or apns_production flag is wrong, not the payload body.How can I help you explore Laravel packages today?