aporat/laravel-appstore-purchases
Laravel package for verifying Apple App Store in-app purchase receipts and subscriptions server-side. Validate transactions, inspect purchase data, and integrate iOS payments into your app with a simple, framework-friendly API.
Subscriptions\ layer treats webhooks and the worker as the source of truth and lets a client credential only extend access, never shorten or grant it. That is the decision that kills the most common revenue-leak and fraud vector in mobile subscriptions (a jailbroken/replayed client claiming a longer expiry), so it's a policy you can commit to rather than re-litigate per feature.subscriptions row per store, keyed against the account) directly supports roadmap items like: cross-platform entitlement display ("subscribed on iOS, not Android"), per-store revenue reporting, and upgrade/downgrade flows — because the data model already separates stores instead of flattening them.VerifyStoreSubscriptions as a command base means "our entitlement state is auditable" can be a standing operational commitment with an SLA, rather than a manual support workflow when a webhook is dropped.SubscriptionRecorder, signed transactions, Play tokens, opt-in StoreKit 1 receipts) is the decision that duplicate purchase submissions don't double-extend a subscription — protects both revenue accounting and your support queue.Events\UnhandledNotification surfaces Apple notification types the package doesn't map (PRICE_CHANGE, MIGRATION, RESCIND_CONSENT, METADATA_UPDATE, plus future types). Product call: you can ship a response to Apple's changes on your timeline, not the package's release cadence — worth writing into the risk section of the roadmap.max-age. This moves "every request mints a token / every push refetches certs" from a scaling liability to a solved problem — it changes how confidently you can size a launch spike.key_path → private_key change plus the aporat/store-receipt-validator ^11.1 floor means an upgrade is a discrete piece of work with a secrets-management migration, best scheduled outside a launch window.Consider it when all of these hold:
Look elsewhere when:
Adoption gate for this specific release: the key_path removal is a hard config break (a config still carrying key_path fails loudly), and APPLE_APPSTORE_KEY_PATH is no longer read. Treat the upgrade as: bump validator to ^11.1, migrate the Apple key to private_key (path or inline PEM from the secrets manager — the inline form and the whitespace/\n repair now make secrets-manager storage viable), add the google_cache block or accept its defaults, and confirm phpseclib is ^3.0.36 so Google RTDN verification doesn't 401 on every push.
For executives: This upgrade turns our in-app subscription handling from client-trusted to store-verified. Apple and Google become the authority on who's paid; our backend records it, and a client can only ever extend access, never grant it — that's direct protection against revenue leakage and refund/chargeback abuse. It also avoids handing a percentage of subscription revenue to a billing middleman while keeping the subscription data in our own database for reporting. The release itself is mostly hardening: shared Google token caching cuts per-request latency and API quota burn, and correctly-signed notifications we don't yet recognise now get recorded and surfaced as an event instead of being rejected and retried forever.
For engineering: Breaking change — key_path is gone from the apple-app-store validator; it's private_key only, accepting either a .p8 path or inline PEM (secrets-manager friendly, with collapsed line breaks repaired before OpenSSL). APPLE_APPSTORE_KEY_PATH is no longer read, and a leftover key_path fails with an explanatory message. Validator floor moves to aporat/store-receipt-validator ^11.1 and the 11.0.0 compatibility path is removed. New hard requirements: phpseclib/phpseclib ^3.0.36 (fixes RTDN push verification returning 401 for everyone when Composer resolved phpseclib 4 — google/auth is phpseclib-3-only and doesn't declare it) and psr/cache ^2.0 || ^3.0. New google_cache config (enabled, store, token_refresh_margin) shares Play access tokens across requests and workers with a cache lock on concurrent misses, falls back to a direct exchange if the store misbehaves, and never logs tokens; disabling it restores per-process minting. Google OIDC certs are cached for their max-age with a once-a-minute refetch on unknown key IDs, and the fetch now has 5s connect / 10s request timeouts — check those against your push endpoint's own timeout budget. Apple notifications of types the validator doesn't know now parse, fully verify, and dispatch UnhandledNotification instead of returning 400 and triggering Apple retries; AppleAppStoreServerNotificationController takes an optional TokenVerifier so tests can use their own signing chain. Composer dist no longer ships tests/ (including test signing keys), .github/, composer.lock, or PHPUnit/PHPStan configs. Finally, the CI matrix previously installed composer.lock in every lane, so the "Laravel 12" lanes were actually running Laravel 13 — worth re-confirming our target framework version against a green lane before we rely on the compatibility claim.
How can I help you explore Laravel packages today?