Download the PHP package kkxdev/laravel-apple-iap without Composer
On this page you can find all versions of the php package kkxdev/laravel-apple-iap. It is possible to download/install these versions without Composer. Possible dependencies are resolved automatically.
Download kkxdev/laravel-apple-iap
More information about kkxdev/laravel-apple-iap
Files in kkxdev/laravel-apple-iap
Package laravel-apple-iap
Short Description Apple In-App Purchase package for Laravel — receipt validation, App Store Server API, JWS verification, and server notifications.
License MIT
Homepage https://github.com/kkxdev/laravel-apple-iap
Informations about the package laravel-apple-iap
Laravel Apple In-App Purchase
A Laravel package for Apple In-App Purchase — receipt validation, App Store Server API (StoreKit 2), JWS transaction verification, and App Store Server Notifications v2.
Core IAP functionality lives in the package. Subscription state management (active/expired/grace period tracking, database records) is left to your application via Laravel events.
Requirements
| Dependency | Version |
|---|---|
| PHP | ^8.0 |
| Laravel | ^8.0 \| ^9.0 \| ^10.0 \| ^11.0 \| ^12.0 |
Installation
Install via Composer:
The service provider and AppleIap facade are auto-discovered via Laravel's package discovery.
Publish the config file:
This creates config/apple-iap.php in your application.
Configuration
Add the following variables to your .env file:
Webhook URL
Register your notification URL in App Store Connect → Your App → App Information → App Store Server Notifications:
The default path can be changed in config:
Circuit Breaker
The circuit breaker is enabled by default and wraps all HTTP calls to Apple's APIs. It opens after 5 consecutive network failures and probes recovery after 60 seconds:
Usage
Facade
All functionality is available through the AppleIap facade:
App Store Server Notifications v2 (Recommended)
Register a webhook route and process incoming notifications:
The apple-iap.verify-notification middleware verifies the cryptographic signature of every incoming notification before your controller runs. Requests with invalid signatures receive a 400 response automatically.
processServerNotification() fires:
ServerNotificationReceived— always (catch-all)- A specific typed event matching the notification type (e.g.
SubscriptionRenewed)
Listening to Events
Register listeners in your EventServiceProvider:
Example Listener
All Available Events
| Event | Fired when |
|---|---|
ServerNotificationReceived |
Every successfully verified notification (catch-all) |
SubscriptionPurchased |
New auto-renewable subscription |
SubscriptionRenewed |
Subscription successfully renewed |
SubscriptionExpired |
Subscription expired (billing retry exhausted or product removed) |
SubscriptionCancelled |
User disabled auto-renew (EXPIRED + VOLUNTARY) |
SubscriptionRevoked |
Family-sharing access revoked |
SubscriptionInBillingRetry |
Payment failed; Apple retrying |
SubscriptionInGracePeriod |
Payment failed but grace period is active |
GracePeriodExpired |
Grace period ended without successful payment |
SubscriptionAutoRenewEnabled |
User re-enabled auto-renew |
SubscriptionAutoRenewDisabled |
User disabled auto-renew (will expire at period end) |
SubscriptionPlanChanged |
User downgraded or upgraded plan |
SubscriptionOfferRedeemed |
Promotional or offer code redeemed |
SubscriptionPriceIncrease |
Apple notified user of price increase |
SubscriptionExpiredPriceIncrease |
Subscription expired because user declined price increase |
ConsumablePurchased |
Consumable IAP purchased |
NonConsumablePurchased |
Non-consumable IAP purchased |
NonRenewingSubscriptionPurchased |
Non-renewing subscription purchased |
RefundIssued |
Refund granted by Apple |
RefundDeclined |
Refund request declined |
RefundReversed |
Previously granted refund reversed |
ConsumptionRequest |
Apple requesting consumption data |
RenewalExtension |
Subscription renewal date was extended |
OneTimeChargePurchased |
One-time charge (e.g. consumable via StoreKit 2) |
ReceiptValidated |
Legacy receipt successfully validated |
TransactionVerified |
JWS transaction decoded and verified |
Every event exposes the raw decoded DTOs — your listener has everything it needs without making additional API calls.
App Store Server API (StoreKit 2)
Get Transaction History
Get Subscription Statuses
Look Up by Order ID
Get Refund History
Extend a Subscription Renewal Date
Send a Test Notification
Verifying JWS Transactions Directly
When your app sends a StoreKit 2 transaction to your server, verify it directly:
Decode renewal info:
Promotional Offer Signatures
Promotional offers let you give discounted (or free) subscription periods to existing or lapsed subscribers. Because generating the signature requires your private key, it must always be done on a secure server — never on the device.
How it works
- Your iOS app determines the user is eligible for a promotional offer.
- The app calls your backend to obtain a signed payload.
- The backend calls
AppleIap::generatePromotionalOfferSignature()and returns the result. - The iOS app passes the four values to StoreKit when initiating the purchase.
- Apple verifies the signature; on success the promotional price applies.
Basic usage
The toArray() response contains exactly the four fields StoreKit requires:
Note: A new
nonceis generated automatically on every call. Each nonce is single-use — Apple rejects duplicate nonces. Signatures expire after 24 hours.
iOS integration (StoreKit 2)
iOS integration (StoreKit 1 / SKPaymentDiscount)
Example controller
Using a dedicated Subscription Key
By default the package reuses the App Store Server API key (APPLE_IAP_KEY_ID / APPLE_IAP_PRIVATE_KEY_PATH). If you want a separate key downloaded from App Store Connect → Users and Access → Keys → In-App Purchase:
applicationUsername rules
| Scenario | Value to pass |
|---|---|
You use appAccountToken UUIDs |
Pass the user's UUID string (lowercase) |
You don't use appAccountToken |
Pass an empty string "" |
You pass null |
Do not do this — causes a double separator and signature mismatch |
Legacy Receipt Validation
Apple has deprecated
verifyReceipt. Use the App Store Server API for new integrations.
The package automatically retries against the sandbox endpoint when Apple returns status 21007 (sandbox receipt sent to production), so you do not need to handle this case yourself.
Dependency Injection
All components are bound in the container. You can inject them directly:
Artisan Command
Verify a receipt from the command line (useful for debugging customer issues):
Error Handling
All exceptions extend Kkxdev\AppleIap\Exceptions\AppleIapException:
| Exception | Thrown when |
|---|---|
ReceiptValidationException |
Apple returns a non-zero receipt validation status |
JwsVerificationException |
A JWS token fails certificate chain or signature verification |
NotificationVerificationException |
A server notification payload fails verification |
ApiException |
App Store Server API returns a 4xx response |
NetworkException |
Connection failure or 5xx response from Apple |
CircuitBreakerOpenException |
Circuit breaker is open due to repeated failures |
InvalidEnvironmentException |
An invalid environment value is configured |
Circuit Breaker
The circuit breaker prevents cascading failures when Apple's APIs are experiencing issues.
States:
- Closed — normal operation; failures are counted
- Open — fail-fast; all requests throw
CircuitBreakerOpenExceptionimmediately - Half-open — one probe request is allowed through to test recovery
What counts as a failure: NetworkException only (5xx responses, connection timeouts). ApiException with 4xx status does not trip the circuit — those are caller errors.
Two independent circuit breakers run in parallel:
receipt_validation— wraps calls toverifyReceiptserver_api— wraps calls to the App Store Server API
To disable entirely:
Testing
The package binds all components through contracts, making them easy to swap in tests.
Faking HTTP calls
Faking events
Mocking the verifier
DTO Reference
JwsTransaction
| Property | Type | Description |
|---|---|---|
transactionId |
string |
Unique transaction identifier |
originalTransactionId |
string |
Original transaction (stable across renewals) |
bundleId |
string |
App bundle identifier |
productId |
string |
Product identifier |
subscriptionGroupIdentifier |
?string |
Subscription group |
purchaseDate |
int |
Purchase timestamp in milliseconds |
originalPurchaseDate |
int |
Original purchase timestamp in milliseconds |
expiresDate |
?int |
Expiry timestamp in milliseconds (subscriptions only) |
quantity |
int |
Quantity purchased |
type |
string |
Product type (see ProductType constants) |
appAccountToken |
?string |
UUID you associated with the user at purchase time |
inAppOwnershipType |
string |
PURCHASED or FAMILY_SHARED |
environment |
?string |
Production or Sandbox |
price |
?int |
Price in milliunits of the currency |
currency |
?string |
ISO 4217 currency code |
revocationDate |
?int |
Set if the transaction was revoked |
revocationReason |
?string |
Reason for revocation |
isUpgraded |
bool |
Whether this was superseded by an upgrade |
Helper methods:
| Method | Signature | Description |
|---|---|---|
matchesBundleId() |
matchesBundleId(): bool |
Returns true if the transaction's bundleId matches APPLE_IAP_BUNDLE_ID from your config. Use this to guard against cross-app transaction replay attacks. |
isExpired() |
isExpired(): bool |
Returns true if expiresDate is in the past. |
isRevoked() |
isRevoked(): bool |
Returns true if revocationDate is set. |
isSandbox() |
isSandbox(): bool |
Returns true when environment is "Sandbox". |
purchaseDateAsDateTime() |
purchaseDateAsDateTime(): \DateTimeImmutable |
Returns purchaseDate as a DateTimeImmutable. |
expiresDateAsDateTime() |
expiresDateAsDateTime(): ?\DateTimeImmutable |
Returns expiresDate as a DateTimeImmutable, or null for non-subscriptions. |
JwsRenewalInfo
| Property | Type | Description |
|---|---|---|
originalTransactionId |
string |
Links to the subscription |
productId |
string |
Current product identifier |
autoRenewProductId |
string |
Product to renew into |
autoRenewStatus |
int |
1 = will renew, 0 = won't |
isInBillingRetryPeriod |
bool |
Payment failed; Apple retrying |
gracePeriodExpiresDate |
?int |
Grace period end in milliseconds |
offerIdentifier |
?string |
Applied offer code |
expirationIntent |
?int |
Why the subscription expired |
Helper methods: willAutoRenew(), isInGracePeriod(), gracePeriodExpiresDateAsDateTime()
ProductType constants
Security
JWS verification uses the Apple Root CA G3 certificate embedded in the package. No certificate is fetched at runtime. The package walks the full x5c certificate chain in every JWS token and verifies:
- Each certificate in the chain is signed by the next.
- The chain root matches the embedded Apple Root CA G3.
- The JWS signature is valid using the leaf certificate's public key (ES256).
This means no Apple notification or transaction token can be forged, even if an attacker controls the network.
License
MIT
All versions of laravel-apple-iap with dependencies
illuminate/support Version ^8.0|^9.0|^10.0|^11.0|^12.0
illuminate/http Version ^8.0|^9.0|^10.0|^11.0|^12.0
illuminate/cache Version ^8.0|^9.0|^10.0|^11.0|^12.0
illuminate/console Version ^8.0|^9.0|^10.0|^11.0|^12.0
illuminate/events Version ^8.0|^9.0|^10.0|^11.0|^12.0
illuminate/routing Version ^8.0|^9.0|^10.0|^11.0|^12.0
firebase/php-jwt Version ^v5.5.1
psr/log Version ^1.0|^2.0|^3.0