Library / SDK
aporat/store-receipt-validator avatar
aporat/store-receipt-validator

store-receipt-validator and the three storefronts your PHP backend actually has to talk to

A modern PHP library for validating in-app purchases from the Apple App Store (including legacy iTunes), Google Play and Amazon Appstore

656 stars148 forksPHPApache-2.0

At a glance

What is it?
The aporat/store-receipt-validator library validates in-app purchases from the Apple App Store, Google Play and Amazon Appstore in one PHP package, with typed responses and offline StoreKit 2 verification. Its most useful design choice is that it models each vendor's API rather than hiding them behind one abstraction.
Who is it for?
store-receipt-validator fits a PHP backend that sells digital goods on more than one storefront and wants each vendor's API modelled accurately rather than flattened into a common shape, especially now that StoreKit 2 apps send a signed transaction rather than an app receipt.
Can I use it commercially?
Yes. Apache-2.0 is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 3 days ago.
What is it written in?
Mainly PHP, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 22, 2026, and from our analysis. They are not legal advice.

Editorial analysis

One package, three storefronts, no common shape forced on them

The aporat/store-receipt-validator package validates in-app purchases from the Apple App Store including legacy iTunes, from Google Play and from the Amazon Appstore, and it supports both production and sandbox environments with detailed response parsing.

What makes the library interesting is what it does not do. It does not define one purchase object for all three stores. Instead each vendor gets its own validator namespace and its own typed response objects, and the README says so directly: each endpoint returns a typed response object, and the App Store Server API documentation is the reference for endpoint semantics.

That is a deliberate choice, and the right one for this domain. A shared abstraction across Apple, Google and Amazon would have to pick the lowest common denominator of three quite different models, and the fields that matter for a purchase decision, transaction history, signed transaction, subscription status, consumption information, are not the same shape on any two of them.

The feature list covers the full surface. Apple App Store Server API v2 support, StoreKit 2 signed transaction verification offline, Apple iTunes Legacy API support which is deprecated by Apple but still available here, Google Play Developer API v3 with subscriptions, one-time products and voided purchases, Real-time Developer Notifications parsing with the Pub/Sub envelope included, Amazon Appstore receipt validation, and App Store Server Notifications v1 and v2 parsing.

The engineering baseline is modern PHP. Strong typing, enums and modern error handling are listed as features, the requirement is PHP 8.4 or newer, and PSR-3 compatible logging is supported. There is a built-in test suite with 100 percent coverage, and the repository carries a phpunit.xml, a phpstan.neon and a phpcs.xml.dist, so the static analysis and coding standard configuration is in the repository rather than assumed.

Installation is one Composer command and a PHP version check

The installation section is a single command, and the requirement above it is the part that will stop you first:

bash
composer require aporat/store-receipt-validator

PHP 8.4 or newer is required. There is no compatibility path here, and that is worth weighing before anything else. On a hosting platform still on 8.2 or 8.3 this package is simply not available, and the choice becomes between raising the floor or validating receipts by hand.

The package is on Packagist, with a stable version badge, a downloads badge, a Codecov badge and a GitHub Actions badge, which is the usual set of signals for a library that runs a CI pipeline on every push and reports coverage.

One routing note belongs at the top rather than the bottom. The README tells Laravel users to use aporat/laravel-appstore-purchases instead. That is a separate package built on top of this library, and it adds Laravel events for App Store and Google Play server notifications, config-based setup and container bindings. In other words, if your application is a Laravel application, this library is the implementation detail and the Laravel package is the interface you should install, because you get the service container wiring and the event handling rather than wiring it yourself.

Apple needs a transaction id, not the app receipt

The quick start for the Apple App Store Server API shows the shape of the whole integration, and one step in it surprises people.

The validator is constructed with named arguments, a signing key, a key id, an issuer id, a bundle id and an environment, and the environment is an enum, with Environment::PRODUCTION used here:

php
$validator = new AppleValidator(
    signingKey: $signingKey,
    keyId: $keyId,
    issuerId: $issuerId,
    bundleId: $bundleId,
    environment: Environment::PRODUCTION
);

The transaction id is not the receipt. The README carries a tip stating that Apple's Server API does not accept the full app receipt and that you should use ReceiptUtility to extract the latest transaction id, and the example does exactly that before calling the API.

That distinction is not cosmetic. The old iTunes receipt verification endpoint took a base64 blob, and the modern Server API takes a transaction identifier and returns the transaction history. Applications that were built against the old flow have to change where the identifier comes from, and the library provides the helper for the transition rather than making you decode the receipt yourself.

The call is getTransactionHistory($transactionId), and it is wrapped in a try block that distinguishes a ValidationException carrying an APIError code, here the invalid transaction id case, from a generic Exception. The response is then read through typed accessors, with getBundleId, getAppAppleId and a loop over getTransactions that reads the product id, the transaction id and, when it is not null, the purchase date as an ISO 8601 string.

There is also a deprecation to note. Validator::validate() is deprecated as of version 9, and the README tells you to use getTransactionHistory() for paginated history, or getTransactionInfo() for a single signed transaction. If you are copying an older tutorial, that method is the one to leave behind.

StoreKit 2 means the client sends a signed transaction instead

This is the part of the library that reflects the current state of the platform rather than the legacy one, and it changes what your mobile app has to send you.

StoreKit 2 apps no longer send an app receipt. Each VerificationResult carries a signed transaction in its jwsRepresentation, which your app can send to your server. The library can verify it offline, and the README is precise about what that check covers: it checks the signature, the Apple certificate chain, and that the payload's bundle id and environment match the validator.

Offline verification is the interesting capability, because it removes a network round trip and a dependency on Apple's availability for the decision that matters most, which is whether a transaction is genuine. The validator is still constructed with the same credentials and the same environment enum:

php
$validator = new AppleValidator(
    signingKey: $signingKey,
    keyId: $keyId,
    issuerId: $issuerId,
    bundleId: 'com.myapp',
    environment: Environment::PRODUCTION
);

and the verification itself is a single call, verifySignedTransaction($jwsRepresentation), with a ValidationException catch for the failure cases.

The distinction in practice is this. If your product still sends an app receipt, you are in the extract transaction id and call the history flow. If it has moved to StoreKit 2, you are in the signed transaction flow, and the same library handles it without a second dependency. A team migrating between the two has one place where that difference lives, which is the kind of thing that is expensive to get wrong when it is spread across application code.

Consumption information and subscription status filters are first class

Two of the less commonly used App Store endpoints are worth looking at, because they show the library is aimed at a server that makes refund and entitlement decisions rather than only confirming a purchase.

The first is sendConsumptionInformation(), which targets Apple's v2 endpoint and requires a delivery status. The request object is built from named arguments:

php
$request = new ConsumptionRequest(
    customerConsented: true,
    sampleContentProvided: false,
    deliveryStatus: DeliveryStatus::DELIVERED,
);

The refund preference is then set as a property, and the consumption percentage is set through a method that sends the value as milliunits, with the example using 40 for 40 percent and the comment noting it goes as 40000. Passing a ConsumptionRequestV1 instead calls the deprecated v1 endpoint.

The second is getAllSubscriptionStatuses(), which accepts an optional status filter so that Apple returns only subscriptions in the given states. The filter is a list of enum cases:

php
$statuses = $validator->getAllSubscriptionStatuses($transactionId, [
    SubscriptionStatus::Active,
    SubscriptionStatus::InBillingGracePeriod,
]);

That filter is what turns a status dump into something you can act on. A billing grace period subscription is one where the user has stopped paying but has not yet lost access, and treating it as cancelled or as active are both wrong.

The rest of the App Store surface is listed in a table in the README. Transactions covers getTransactionHistory, getTransactionInfo, getAppTransactionInfo, finishTransaction, setAppAccountToken and sendConsumptionInformation. Order and refunds cover lookUpOrderId and getRefundHistory. Subscriptions cover getAllSubscriptionStatuses, extendSubscriptionRenewalDate, extendSubscriptionRenewalDatesForAllActiveSubscribers and getStatusOfSubscriptionRenewalDateExtensions. Notifications cover requestTestNotification, getTestNotificationStatus and getNotificationHistory.

Google, Amazon and the notification side of the problem

The Apple side is the most detailed part of the documentation, but the library covers all three storefronts, and the Google and Amazon entries are where the shape of the library shows.

For Google it supports the Developer API, the Android Publisher v3, for subscriptions, one-time products and voided purchases. It also parses Real-time Developer Notifications, and the Pub/Sub envelope is included, which means the library accepts the wrapped message that arrives on a Google Cloud Pub/Sub topic rather than only the bare payload.

That last detail is the difference between a library that is useful in production and one that is not. A backend that receives subscription lifecycle events from Google gets a Pub/Sub envelope with the notification inside it, and if your parser expects the inner payload you have a wrapper to write before you can use anything. Here it is handled.

For Amazon, the support is described more briefly as Amazon Appstore receipt validation, and the examples directory shows how that is meant to be used, with separate example files for the Amazon receipt, the Apple App Store receipt, the iTunes receipt, the Google Play purchase, and the two server notification formats, the App Store one and the iTunes one.

The server notification parsing deserves a mention because it is easy to overlook. A store tells your server about a purchase, a renewal, a refund or a chargeback through a notification rather than only through the client. The library parses App Store Server Notifications in both v1 and v2, and the iTunes notification, and the Laravel companion package turns those notifications into events. So the library covers both directions of the flow, the client confirming a purchase and the server learning about it later.

Where store-receipt-validator is the wrong tool

The first wrong case is PHP below 8.4, and it is not a soft requirement. The library is built on the language features that arrived in 8.4, so on 8.2 or 8.3 the choice is between raising the platform floor and writing the receipt handling yourself. On a shared hosting platform or an internal application with a fixed runtime, that is often the deciding constraint, and it is worth checking before anything else.

The second wrong case is a Laravel application, where using this library directly means hand-wiring the service container and implementing the notification handling that the companion package already does as events. The README says so plainly, and it is advice worth following: install aporat/laravel-appstore-purchases and let it sit on top of this library.

The third is a product that has moved entirely to StoreKit 2 and only talks to one storefront. If you have a single iOS app and nothing else, the amount of abstraction you inherit from a three-store library is a cost with no offsetting benefit, and the offline verification alone is a small enough surface to implement directly against Apple's documentation.

The alternative worth naming is the vendors' own server libraries, and the difference in approach is instructive rather than a matter of taste. Apple's official libraries cover Apple's APIs and nothing else, and the Google ones cover Google's, and each is the canonical reference for its own platform. This library's bet is that a team selling on several storefronts prefers one consistent PHP interface with typed responses, one error model and one logging integration over three vendor SDKs with three exception hierarchies, at the price of depending on someone else's mapping of those APIs, including the parts that are still in flux as Apple moves StoreKit 2 forward. If you sell on one store and want the reference implementation exactly, the vendor library is the better answer. If you sell on three and your team would rather not learn three exception models, this package is.

Maintenance state, version cadence and what the changelog has to keep up with

The repository is not archived and the last push was on 2026-10-20. The recent releases are 9.0.0 on 2026-05-24, 10.0.0 on 2026-09-07 and 11.0.0 on 2026-10-02, so the version line is on 11 and the project is shipping major versions every few months.

That cadence is not a style choice, it is a consequence of the domain. Each of these libraries tracks a vendor API that moves, and Apple's App Store Server API has already had a v2 endpoint with a v1 still in use, as the ConsumptionRequest and ConsumptionRequestV1 pair shows. A major version here is a signal that the mapping to the vendor APIs has changed in a way that matters, and the deprecation of Validator::validate() as of version 9 is the same kind of event, a method removed from the recommended path.

The upgrade cost is therefore higher than a typical library of this size, and that is honest to state. When a major version lands, the class and method names in your code may have moved, and you have to read the changelog to find out which. The mitigation is that the coverage claim, the static analysis configuration and the test suite are all in the repository, so you can run the tests against your own fixtures and see what breaks before you ship it.

The licence is Apache-2.0, with the LICENSE file at the repository root. For a library that handles purchase verification inside a commercial application, the permissive terms and the explicit grant of patent rights are the useful part, though how you use it alongside each vendor's own terms remains a question for your own review.

The one thing the visible documentation does not tell you is how the sandbox environments behave in detail, or what the error taxonomy covers beyond the APIError codes. Both are in the source and the changelog, and both are worth reading before you rely on this to grant entitlements.

Editorial conclusion

store-receipt-validator fits a PHP backend that sells digital goods on more than one storefront and wants each vendor's API modelled accurately rather than flattened into a common shape, especially now that StoreKit 2 apps send a signed transaction rather than an app receipt. It is the wrong choice if you are on PHP below 8.4, since that is a hard requirement, and the wrong choice for a Laravel application, where the companion package does the work with events and container bindings. Before you adopt it, decide whether your product sends an app receipt or a StoreKit 2 jwsRepresentation, because the library treats those as different entry points, and read the deprecation note on Validator::validate().

Frequently asked questions

Which stores does store-receipt-validator support?

It validates in-app purchases from the Apple App Store including legacy iTunes, from Google Play and from the Amazon Appstore, and it supports both production and sandbox environments. It also parses App Store Server Notifications v1 and v2 and Google Play Real-time Developer Notifications.

What PHP version does store-receipt-validator require?

PHP 8.4 or newer is required. It lists strong typing, enums and modern error handling among its features, and there is no compatibility path for older runtimes.

How do I install store-receipt-validator?

With composer require aporat/store-receipt-validator. If you are building on Laravel, the README directs you to aporat/laravel-appstore-purchases instead, which is built on this library and adds events, config-based setup and container bindings.

How do I verify a StoreKit 2 transaction in store-receipt-validator?

Call verifySignedTransaction with the jwsRepresentation your app sends. The README says the offline check covers the signature, the Apple certificate chain, and that the payload's bundle id and environment match the validator.

What replaced Validator::validate() in store-receipt-validator?

It is deprecated as of version 9. The README directs you to getTransactionHistory() for paginated history, or getTransactionInfo() for a single signed transaction.

Official sources

  1. aporat/store-receipt-validator on GitHub
  2. Issues
  3. License: Apache-2.0
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/aporat-store-receipt-validator.svg)](https://hysenlabs.com/projects/aporat-store-receipt-validator)