johnkhansrc/iaphub-bundle
A Symfony bundle for integrating IAPHub’s in-app purchase and subscription platform, providing service configuration and convenient access to API features for validating receipts and managing products and subscriptions.
This package is a Symfony bundle wrapping the IAPHub PHP SDK — not a Laravel package. It ships no service provider, no facade, and no config/ publish tag, so "installation" is really "install the dependency and wire it yourself."
composer require johnkhansrc/iaphub-bundle
Before writing any code, open three things in the package repo:
composer.json — the php constraint and the symfony/* version range. This is the single most important file after a v3 release; it determines whether the package can even be installed alongside your Laravel version.DependencyInjection/ (the Extension and Configuration classes) — this is where the bundle declares its config keys and the service ID it registers the client under. Mirror those key names in your own config/iaphub.php so the two don't drift.iaphub/iaphub-php-sdk — the real API surface (getUser(), subscriptions, environment constants) lives there, and that is what you'll actually be calling.First use case: gating premium features on a validated mobile subscription. A minimal Laravel wiring looks like:
// app/Providers/IAPHubServiceProvider.php
public function register(): void
{
$this->app->singleton(\IAPHub\IAPHub::class, fn () => new \IAPHub\IAPHub(
config('iaphub.app_id'),
config('iaphub.api_key'),
config('iaphub.env'), // production | sandbox
));
}
// config/iaphub.php
return [
'app_id' => env('IAPHUB_APP_ID'),
'api_key' => env('IAPHUB_API_KEY'),
'env' => env('IAPHUB_ENV', 'sandbox'),
];
Then resolve the client once and read a user's subscriptions by your own app user ID. Keep this behind a small wrapper class rather than injecting the vendor client into controllers.
Always wrap the vendor client. The bundle will not give you an interface to type-hint against, and the SDK's return shapes are generic objects. Put a SubscriptionService in front of it that returns your own DTOs (Entitlement, SubscriptionStatus). When IAPHub changes or the bundle gets abandoned, you rewrite one class.
Resolve credentials per environment, not per request. Bind the client as a singleton and read app_id/api_key/env from config only. Sandbox and production credentials should be separate env vars so a staging deploy can never validate against production.
Cache aggressively; the API is a network hop. A naive getUser() on every request will dominate your response time and burn rate limit. Cache the resulting entitlement per user ID (Redis, short TTL — a few minutes) and invalidate from your webhook handler rather than relying on TTL alone.
Reconcile via webhook, don't poll. IAPHub pushes subscription lifecycle events. Expose a route, verify the payload, and dispatch a queued job that re-fetches the user and updates your local subscriptions table. That table — not the live API — should be the source of truth for entitlement checks in hot paths.
Centralise the entitlement check. One User::hasActiveSubscription() (or a Gate::define('premium', ...)) reading the cached/local state. Middleware, API resources, and Blade directives should all funnel through it, so the "is the cache stale?" question has exactly one answer.
Testing. Because the vendor client is concrete, bind a fake in TestCase::setUp() against your wrapper's interface, or Http::fake() the IAPHub endpoints if the SDK uses Guzzle. Never let the test suite reach the real API — it will, silently, with your sandbox key.
Consider dropping the bundle. If you are not on Symfony, johnkhansrc/iaphub-bundle contributes only a DI extension you cannot use. composer require iaphub/iaphub-php-sdk gives you the identical runtime capability with one fewer transitive dependency and one fewer package to track for security advisories.
Treat v3.1.0 as a breaking change, because the changelog won't tell you. The only entry is "Chore/upgrade symfony," yet the version jumps 2.0.1 → 3.1.0. A major bump labelled as a chore almost always means a raised minimum PHP version and/or dropped Symfony majors. Diff the composer.json between the two tags before upgrading — if it now requires PHP 8.2+ you may be pinned out of your Laravel version, and if it dropped Symfony 5 support, older Laravel/Symfony interop paths break. The 3.0.x line has no release notes at all, so you cannot infer what else moved; read the commit history.
It is a bundle, so Laravel's package discovery will not help you. There is no extra.laravel.providers entry and no auto-registration. If you assume composer require is enough, you'll get a container binding error rather than a helpful message.
Symfony config semantics don't carry over. The bundle's Configuration class defines tree nodes with defaults and validation; Laravel's config() has neither. If you copy key names across, also copy the defaults and add explicit validation (config('iaphub.env') returning null because someone typo'd IAPHUB_ENV is a classic).
config:cache will bite you if you call env() outside config files. Standard Laravel trap, but it hits this integration hard because the client is usually constructed lazily inside a service that reaches straight for env('IAPHUB_API_KEY'). It works locally and returns null in production.
Fail-open vs fail-closed is a product decision, not a code one. If IAPHub times out, does the paying customer lose access? Decide once, encode it in the wrapper (catch, log, fall back to last-known local state), and set an explicit HTTP timeout — the SDK default may be longer than your request budget.
Pass your own stable user ID. IAPHub keys users by the identifier you supply. Device IDs, IDFVs, or email addresses will orphan subscriptions on reinstall or email change. If you have legacy users, you need a migration path before you can reconcile webhooks.
The API key is a server secret. Nothing in this package prevents it ending up in a VITE_/MIX_ variable or a mobile bundle. Keep it server-side only, and rotate it if you ever shipped it.
Extension points are thin. You can't decorate bundle services that Laravel never registered. Your seam is your own wrapper — subclass it, swap its implementation, or replace the binding in a test service provider. Don't patch the vendor tree; the next minor release overwrites it and you'll lose the fix.
How can I help you explore Laravel packages today?