NewYour coding agent can read the release notes before it upgrades.Set up the MCP server →
Packagist · #4275 most downloaded on Packagist
PHP receipt validator for Apple App Store, Google Play and Amazon Appstore
Last release 5 days ago
02 Oct 2026
Release timing varies
gaps range from 2 weeks to 12 months
Nearly every release is documented
notes for 60 of 60 stable releases
Nothing withdrawn
no release was ever pulled
12 years old
60 releases · first in 2014
AppleAppStore\ConsumptionRequestV1 for the deprecated v1 Send Consumption Information endpoint, and DeliveryStatus / RefundPreference enums for v2 ( #…
AppleAppStore\Validator::verifySignedTransaction(), verifySignedRenewalInfo() andverifySignedAppTransaction() verify StoreKit 2 JWS payloads offline, checking the signature,AppleAppStore\Validator::verifyNotification() verifies an App Store Server Notification andServerNotificationappAppleId on the AppleAppStore\Validator constructor, with setAppAppleId() andgetAppAppleId(). When set, production app transactions and notifications must carry the matchingAppleAppStore\Signature namespace with the four signing helpers Apple's official librariesPromotionalOfferV2SignatureCreator, IntroductoryOfferEligibilitySignatureCreator,AdvancedCommerceInAppSignatureCreator (JWS for StoreKit 2) andPromotionalOfferSignatureCreator (StoreKit 1 ECDSA signature)AppleAppStore\ConsumptionRequestV1 for the deprecated v1 Send Consumption Information endpoint,DeliveryStatus / RefundPreference enums for v2ConsumptionRequest::setConsumptionPercent() converts a plain percentage to the milliunits ApplegetAllSubscriptionStatuses(), sent as repeated status queryUser-Agent: store-receipt-validator/php/<version> on App Store Server API requests, with theAbstractValidator::userAgent() is public for reuseServerNotification::getAppAppleId(), and an optional TokenVerifier constructor argument forEndpointPathsTest, a single table of every App Store Server API endpoint with its verb,Transaction exposes the rest of the documented RVS response: cancelReason,fulfillmentDate, fulfillmentResult, betaProduct, promotions, deferredDate, deferredSku,countryCode, baseReceipts, purchaseMetadataMap and parentProductId, with ProductType,CancelReason, FulfillmentResult, PromotionType and PromotionStatus enums and a PromotionisEntitled(), isCanceled(), getExpiresAt(), isInFreeTrial(),isInGracePeriod(), isQuickSubscribe(), isAddOnSubscription() and getActivePromotion() onTransaction, with the most common ones mirrored on Response, plus Response::getTransaction()Amazon\Validator::validate() accepts the receipt ID and user ID as arguments, and getUserId()getReceiptId() read them backAmazon\APIError::fromException(), isCanceledReceipt() and isRetryable() let callers tell aAccept and User-Agent headers on Amazon RVS requestsPendingRefundReviewNotification for the pendingRefundReviewNotification RTDNServerNotification::getPendingRefundReviewNotification()isPendingRefundReviewNotification()SubscriptionNotificationType cases ITEMS_CHANGED (17), CANCELLATION_SCHEDULEDPRICE_STEP_UP_CONSENT_UPDATED (22); PRICE_CHANGE_CONFIRMED (8) is marked deprecatedGooglePlay\Validator::getProductPurchaseV2() for purchases.productsv2, the current one-timeProductLineItem per product with quantity, purchase option,ProductPurchaseV2 responseGooglePlay\Validator::cancelSubscription() for purchases.subscriptionsv2.cancel, with theSubscriptionCancellationType enum (USER_REQUESTED_STOP_RENEWALS orDEVELOPER_REQUESTED_STOP_PAYMENTS)GooglePlay\Validator::deferSubscription() for purchases.subscriptionsv2.defer, taking theDateInterval, with validateOnly for dryDeferSubscriptionResponse with the new expiry per productSubscriptionPurchase::getEtag(), needed by deferSubscription()ProductPurchaseState::fromV2String() and ConsumptionState::fromV2String() map the stringpurchases.productsv2 onto the existing integer-backed casesGooglePlay\Validator::getOrder(), getOrders() (up to 1000 IDs, sent as repeated orderIdsrefundOrder() (with the revoke flag Google recommends for failedreviewRefund() for the orders resource. Order exposes state, sales channel,OrderLineItem per product with listing price,ReviewRefundRequest and RefundPreference for answering a chargeback review, withwithConsumptionPercent() to convert a plain percentage to milliunitsGooglePlay\APIException (extends ValidationException) is thrown for non-2xx responses andgetStatusCode(), getReason(), getError() and isRetryable()GooglePlay\Money value object with getAmount(), getAmountMicros(), toDecimalString(),isZero() and toArray()SubscriptionLineItem reads the rest of the documented schema: getOfferPhase() withisInFreeTrial() / isInIntroductoryPrice(), getProrationOriginalOfferPhase(),getSignupPromotionType() / getPromotionCode(), getItemReplacement(),hasDeferredItemRemoval(), getInstallmentPlan() and getPriceStepUpConsentDetails(), withSubscriptionOfferPhase, SignupPromotionType, ReplacementMode, ConsentState,PriceChangeMode and PriceChangeState enums and ItemReplacement, InstallmentPlan,PriceStepUpConsentDetails and PriceChangeDetails value objectsSubscriptionPurchase::getRenewalDeclinedOrderId() / isRenewalDeclined() (from the on-holdgetOutOfAppExpiredPurchaseToken() and isInFreeTrial()CanceledStateContext with CancellationSource and CancelSurveyReason enums, andSubscribeWithGoogleInfo, replacing the raw arraysacknowledgeSubscription() accepts optional obfuscated account and profile IDs, sent asexternalAccountIdsSubscriptionLineItem::getRecurringPrice() andPriceChangeDetails::getNewPrice() return Money instead of a raw array,getPriceChangeDetails() returns PriceChangeDetails, andSubscriptionPurchase::getCanceledStateContext() / getSubscribeWithGoogleInfo() return typedgetRawData()GooglePlay\APIException; it extends ValidationException,api.storekit.apple.com and api.storekit-sandbox.apple.com, instead of the legacy*.itunes.apple.com names. Add the new hosts to any egress allowlist before upgradingsendConsumptionInformation() targets the v2 endpoint/inApps/v2/transactions/consumption) again, as it did in 9.0.0 before 10.0.0 reverted it toConsumptionRequestdeliveryStatus and refundPreference take the new enums, and legacy v1deliveryStatus is required by v2, so a request withoutConsumptionRequestV1 to keep using the v1 endpointServerNotification reads the bundle ID, environment and app Apple ID from whichever payloaddata, summary, appData or externalPurchaseToken). Summary andTokenVerifier now requires the App Store signing marker on the leaf certificate,x5c chain of exactly three certificates, and everysignedDate. Payloads signed with a chain that does notphpseclib/phpseclib constraint is ^3.0 || ^4.0. ReceiptUtility selects the matching ASN.1Amazon\APIError is keyed by HTTP status code (400, 410, 429, 496, 497 and 500).fromString() is removed and message() is keptAmazon\Response::getProductType() and the productType property hold aProductType enum instead of a stringAmazon\Response wraps a single Transaction and delegates to it instead of parsing the body aCANCEL_SURVEY_REASON_TOO_EXPENSIVE; the documentedCANCEL_SURVEY_REASON_COST_RELATEDServerNotification rejected pendingRefundReviewNotification pushes with aacknowledgeSubscription() and acknowledgeProduct() sent [] as the request body{}getAppTransactionInfo() called /inApps/v1/transactions/appTransaction/{id} since 10.0.0; theappTransactionsautoRenewing and gracePeriodEndDate were read with a leading capital letter, whichisAutoRenewing() was always false and getGracePeriodEndDate() alwaysAmazon\Response::getUserId() always returned null because RVS does not echo the user ID. ThesetDeveloperSecret() methodOne column per quarter.
New ReceiptValidator\GooglePlay namespace covering the Google Play Developer API (Android Publisher v3). GooglePlay\Validator is constructed with (str
ReceiptValidator\GooglePlay namespace covering the Google Play Developer API (AndroidGooglePlay\Validator is constructed with (string $packageName, ServiceAccountCredentials|string|null $credentials = null, Environment $environment = Environment::PRODUCTION) and exposes getSubscriptionPurchaseV2(), acknowledgeSubscription(),revokeSubscription(), getProductPurchase(), acknowledgeProduct(), consumeProduct() andgetVoidedPurchases(), plus validate(?string $purchaseToken = null) (agetSubscriptionPurchaseV2() alias), setPurchaseToken(), getPackageName(),setAccessTokenProvider() and getAccessTokenProvider().GooglePlay\JWT\AccessTokenProvider (interface),ServiceAccountCredentials (fromJson(), fromArray()), ServiceAccountTokenProvider (OAuthgetAccessToken(), clearCache(),createAssertion()) and CallbackAccessTokenProvider for plugging in an external library such asgoogle/auth. (#227)SubscriptionPurchase (extends AbstractResponse; getLineItems(),getLatestLineItem(), getExpiryTime(), getProductIds(), isEntitled(), isAutoRenewing(),SubscriptionLineItem, ProductPurchase and VoidedPurchase (extendAbstractTransaction), VoidedPurchasesResponse, VoidedPurchasesParams, RevocationContextfullRefund(), proratedRefund(), itemBasedRefund()), and a string-backedGooglePlay\APIError enum with message(), isRetryable() and fromString().SubscriptionState, AcknowledgementState, ConsumptionState,ProductPurchaseState, PurchaseType, RefundType, VoidedProductType, VoidedPurchaseType,VoidedReason, VoidedSource, SubscriptionNotificationType andOneTimeProductNotificationType.GooglePlay\ServerNotification (with fromPubSubMessage() toSubscriptionNotification, OneTimeProductNotification andVoidedPurchaseNotification. (#227)Support\ValueCasting::toDateFromRfc3339() protected helper for parsing Google's RFC 3339CarbonImmutable.phpseclib/phpseclib requirement raised from ^3.0 to ^4.0, andAppleAppStore\ReceiptUtility was ported to phpseclib 4's typed ASN.1 maps. The public signaturesextractTransactionIdFromAppReceipt() and extractTransactionIdFromTransactionReceipt() areValueError (wrapping the phpseclibid-signedData OID name is accepted alongside the numeric OID.^3.0 || ^4.0,^4.0)guzzlehttp/psr7 requirement widened from ^2.6 to ^2.6 || ^3.0.AbstractValidator::validate() is no longer abstract: the default implementation throws aValidationException directing callers to the store-specific endpoint methods (used byGooglePlay\Validator). Custom subclasses are no longer forced to implement it.AppleAppStore\NotificationHistoryItem::wasDelivered() now returns whether the mostfalse whenAppleAppStore\Validator::getAppTransactionInfo() was changed to/inApps/v1/transactions/appTransaction/{transactionId} instead of the plural/inApps/v1/transactions/appTransactions/{transactionId} used in 9.0.0. Apple does not serve theAppleAppStore\Validator::sendConsumptionInformation() was changed toPUT /inApps/v1/transactions/consumption/{transactionId} instead of the v2 path used in 9.0.0,ConsumptionRequest kept sending v2-only fields such as consumptionPercentage; the v2NotificationHistoryItem change or the two App Store endpoint path changes above.AppleAppStore\NotificationHistoryItem::$firstSendAttemptResult andgetFirstSendAttemptResult() were removed; inspect getSendAttempts() instead. (commit b7e77abNotes written from the code diff for this release; see CHANGELOG.md.
AppleAppStore\Validator::validate() is deprecated in favour of getTransactionHistory() (paginated history with filters) or getTransactionInfo() (a sin…
AppleAppStore\Validator: getTransactionHistory(string $transactionId, ?TransactionHistoryParams $params = null): Response (GET /inApps/v2/history/{id}), getTransactionInfo(string $transactionId): Transaction (GET /inApps/v1/transactions/{id}, JWS-verified), getAppTransactionInfo(string $transactionId): AppTransaction (GET /inApps/v1/transactions/appTransactions/{id}), finishTransaction(string $transactionId): void (POST /inApps/v1/transactions/{id}/finish) and setAppAccountToken(string $originalTransactionId, string $appAccountToken): void (PUT /inApps/v1/transactions/{id}/appAccountToken; the token must be a UUID v4).getAllSubscriptionStatuses(string $originalTransactionId): SubscriptionStatusResponse (GET /inApps/v1/subscriptions/{id}),getRefundHistory(string $transactionId, ?string $revision = null): RefundHistoryResponse (GET /inApps/v2/refund/lookup/{id}), sendConsumptionInformation(string $transactionId, ConsumptionRequest $request): void (PUT /inApps/v2/transactions/consumption/{id}),extendSubscriptionRenewalDate(string $originalTransactionId, ExtendRenewalDateRequest $request): ExtendRenewalDateResponse (PUT /inApps/v1/subscriptions/extend/{id}),extendSubscriptionRenewalDatesForAllActiveSubscribers(MassExtendRenewalDateRequest $request): string (POST /inApps/v1/subscriptions/extend/mass, returns the request identifier) andgetStatusOfSubscriptionRenewalDateExtensions(string $productId, string $requestIdentifier): MassExtendRenewalDateStatusResponse.lookUpOrderId(string $orderId): OrderLookupResponse (GET /inApps/v1/lookup/{orderId}, not available in sandbox), getTestNotificationStatus(string $testNotificationToken): CheckTestNotificationResponse (GET /inApps/v1/notifications/test/{token}) and getNotificationHistory(NotificationHistoryRequest $request, ?string $paginationToken = null): NotificationHistoryResponse (POST /inApps/v1/notifications/history).ReceiptValidator\AppleAppStore: TransactionHistoryParamstoQueryParams(); revision, date range, productId/productType/subscriptionGroupIdentifierNotificationHistoryRequest (toArray()),ConsumptionRequest, ExtendRenewalDateRequest and MassExtendRenewalDateRequest.AppTransaction, CheckTestNotificationResponse,SendAttemptItem, NotificationHistoryResponse, NotificationHistoryItem (includingdecodeNotification(): ServerNotification), OrderLookupResponse, RefundHistoryResponse,SubscriptionStatusResponse (with getAllLastTransactions()), SubscriptionGroupStatusItem,LastTransactionItem, ExtendRenewalDateResponse, MassExtendRenewalDateStatusResponse, and theSubscriptionStatus enum with label().AppleAppStore\Validator::makeRawRequest(string $method, string $uri = '', array $queryParams = [], ?array $requestBody = null): array sends JSON request bodies and treats a200 with an empty body as success; makeRequest() is now a thin wrapper that builds aResponse.AppleAppStore\Validator::validate() signature is now validate(?string $transactionId = null, ?TransactionHistoryParams $params = null): Response and delegates to getTransactionHistory();DESCENDING.productId=a&productId=b)query array is also included inphpunit/phpunit dev requirement raised from ^12.0 to ^13.0.getAppTransactionInfo(), setAppAccountToken() and getAllSubscriptionStatuses() from/inApps/v2/... to /inApps/v1/..., changed the app-transaction path to/inApps/v1/transactions/appTransactions/{transactionId} with a required $transactionIdsignedAppTransactionInfo instead of signedTransactionInfo. All threevalidate() still calls /inApps/v2/history/{id} andrequestTestNotification() still calls POST /inApps/v1/notifications/test.AppleAppStore\Validator::validate() is deprecated in favour of getTransactionHistory()getTransactionInfo() (a single transaction). It still works.Notes written from the code diff for this release; see CHANGELOG.md.
Version 9.0.0 of the Google Play Billing Library and Kotlin extensions are now available. See the PBL 9 migration guide if you want to migrate from the previous versions of PBL.
Updated error codes for blocked Play Store activity : Error codes for blocked Play Store apps have been updated. For instances where the Play Store app is blocked by the system (for example, in OEM-customized kids mode), the response code has changed from ERROR to BILLING_UNAVAILABLE . Additionally, the BillingResult for such cases now provides a Play Store is blocked debug message. Note: For this feature to work, you need AndroidX.core library version 1.9 or later.
Nullability update for developer-provided billing : The DeveloperProvidedBillingDetails.getLinkUri() method has been updated to be @Nullable . This change supports scenarios where the direct link URI for external payments is unavailable during the payment selection stage.
To handle this change safely, ensure your integration code handles both null and empty string ( "" ) values from the DeveloperProvidedBillingDetails.getLinkUri() method before parsing or launching browser intents.
Updated targetSdkVersion to 35.
You can now use in-app messaging to notify users of an upcoming opt-in price increase. This lets users confirm the price increase without leaving the app. The message for an outstanding opt-in price increase is shown starting on the first day the user can accept the price increase, and the message is shown a maximum of once every 7 days.
AbstractValidator::setLogger(LoggerInterface $logger): static adds PSR-3 logging to all validators; the default is NullLogger . The Apple App Store, i
AbstractValidator::setLogger(LoggerInterface $logger): static adds PSR-3 logging to allNullLogger. The Apple App Store, iTunes and Amazon validators logdebug on each request, info on success (and on the iTunes sandbox/production retry), warningerror on connection failures.AbstractValidator::setHttpClient(Psr\Http\Client\ClientInterface $client): static injects anyGuzzleHttp\Client to GuzzleHttp\ClientInterface)getRequestFactory(): RequestFactoryInterface and getStreamFactory(): StreamFactoryInterface (PSR-17) with protected $requestFactory/$streamFactory properties,GuzzleHttp\Psr7\HttpFactory. There are no public setters for the factories;setHttpClient() dropped its ?string $baseUri second parameter and now requires aClientInterface (Guzzle's Client still qualifies). Requests are now sent with absolutebase_uri configured on an injected Guzzle client is ignored.AbstractValidator now declares a constructor (it initialises the logger); customparent::__construct().Psr\Http\Client\ClientExceptionInterface insteadGuzzleHttp\Exception\GuzzleException; they are still rethrown as ValidationException withprevious.setEnvironment() and setHttpClient() now return static instead of self.guzzlehttp/guzzle and guzzlehttp/psr7 remain required dependencies (composer.json is unchangedAbstractValidator::getBaseUri() and the protected $baseUri property were$client_options array (Guzzle timeout, connect_timeout,http_errors) was removed; the 30-second timeouts and http_errors => false are now hardcoded inNotes written from the code diff for this release; see CHANGELOG.md.
Version 8.0.0 of the Google Play Billing Library and Kotlin extensions are now available.
In-app items will now be referred to as one-time products .
Multiple purchase options and offers for one-time products.
You can now have multiple purchase options and offers for your one-time products. This provides you flexibility in how you sell your products and reduces the complexity of managing them.
Improved the queryProductDetailsAsync() method.
Prior to PBL 8.0.0, the queryProductDetailsAsync() method didn't return products that couldn't be fetched. This could be due to reasons such as the product is not found or no offers are available to the user. With PBL 8.0.0, unfetched products are returned with a new product-level status code that provides information about unfetched products. Note that there is a change in the signature of the ProductDetailsResponseListener.onProductDetailsResponse() which requires changes in your app. For more information, see process the result .
Automatic service reconnection.
The new BillingClient.Builder.enableAutoServiceReconnection() builder parameter lets developers opt-in to automatic service reconnection, which simplifies connection management by handling reconnections to the Play Billing Service automatically and eliminating the need to manually call startConnection() in the event of a service disconnection. For more information, see Automatically Re-establish a Connection .
Sub-response codes for the launchBillingFlow() method.
The BillingResult returned from launchBillingFlow() will now include a sub-response code field. This field will only be populated in some cases to provide a more specific reason for the failure. The sub-response field can have the following values:
PAYMENT_DECLINED_DUE_TO_INSUFFICIENT_FUNDS - Returned when the user's funds are less than the price of the item they are attempting to purchase.
USER_INELIGIBLE - Returned when the user doesn't meet the configured eligibility requirements for a subscription offer.
NO_APPLICABLE_SUB_RESPONSE_CODE - The default value, returned when no other sub-response code is applicable.
Removed the queryPurchaseHistory() method.
The queryPurchaseHistory() method that was previously marked as deprecated has now been removed. See Query Purchase History for details on what alternative APIs to use instead.
Removed the querySkuDetailsAsync() method.
The querySkuDetailsAsync() method that was previously marked as deprecated has now been removed. You should use queryProductDetailsAsync instead.
Removed the BillingClient.Builder.enablePendingPurchases() method.
The enablePendingPurchases() method with no parameters that was previously marked as deprecated has now been removed. You should use enablePendingPurchases(PendingPurchaseParams params) instead. Note that the deprecated enablePendingPurchases() is functionally equivalent to enablePendingPurchases(PendingPurchasesParams.newBuilder().enableOneTimeProducts().build()) .
Removed the overloaded queryPurchasesAsync() method that takes a skuType .
The queryPurchasesAsync(String skuType, PurchasesResponseListener listener) method that was previously marked as deprecated has now been removed. Alternately, use queryPurchasesAsync(QueryPurchasesParams queryPurchasesParams, PurchasesResponseListener listener) .
The legacy verifyReceipt / V1 notification classes are marked @deprecated in favour of their AppleAppStore counterparts: iTunes\Validator , iTunes\Res…
^8.3 to ^8.4. (commit 830c893)AppleAppStore\RenewalInfo and iTunes\RenewalInfo are now final readonlyAppleAppStore\RenewalInfo was a non-finalAbstractRenewalInfo is nowabstract readonly. $rawData on both is a non-nullable array, andAppleAppStore\RenewalInfo::$eligibleWinBackOfferIds is normalised to a list<string>.declare(strict_types=1) added to the remaining source files (enums, JWT classes,ValidationException, ValueCasting).phpstan/phpstan-phpunit now allows ^1.4 || ^2.0, squizlabs/php_codesniffer^3.10 || ^4.0, and a phpstan.neon (level 8) was added.phpstan/phpstan constraint has been ^1.11 || ^2.0allow-plugins set for php-http/discovery, andlint, lint:fix, test:coverage, ci).@deprecated in favour of theirAppleAppStore counterparts: iTunes\Validator, iTunes\Response, iTunes\Transaction,iTunes\RenewalInfo, iTunes\APIError, iTunes\ServerNotification andiTunes\ServerNotificationType.Notes written from the code diff for this release; see CHANGELOG.md.
Version 7.1.0 of the Google Play Billing Library and Kotlin extensions are now available.
Improved thread safety related to connection status and management.
Introduced partial changes for testing BillingResult response codes which is fully released in Play Billing Library 7.1.1. To test your integration using this feature, you'll need to upgrade to Play Billing Library 7.1.1. A bug exists that will only impact applications with billing overrides testing enabled and does not affect regular usage. For more information, see Test BillingResult response codes .
AbstractValidator::setHttpClient(GuzzleHttp\Client $client, ?string $baseUri = null): self and getBaseUri(): ?string for injecting a preconfigured HTT
AbstractValidator::setHttpClient(GuzzleHttp\Client $client, ?string $baseUri = null): self andgetBaseUri(): ?string for injecting a preconfigured HTTP client.Support\ValueCasting trait (toString(), toInt(), toBool(), toDateFromMs()) and theAbstractRenewalInfo base class shared by both RenewalInfo models.APIError enums gain message() on each case; AppleAppStore\APIError adds isRetryable() andfromInt(), iTunes\APIError adds fromInt(), Amazon\APIError adds fromString().AppleAppStore\ServerNotificationType adds an UNKNOWN case, fromString() (returns UNKNOWNisSubscriptionLifecycle(), isRefundRelated() andisOfferOrPriceEvent(); ServerNotificationSubtype adds UNKNOWN, fromString(),isSubscriptionChange(), isBillingRelated(), isPriceChange() and isRefundReversal().Amazon\Response gains getReceiptId(), getProductId(), getUserId(), getProductType(),getPurchaseDate(), getCancellationDate() and isTestTransaction(); iTunes\RenewalInfo gainsgetExpirationReason(): ?string and hasExpirationIntent(): bool; AppleAppStore\TransactiongetEnvironment(). (#200)AppleAppStore\Transaction::$purchaseDate,AppleAppStore\Response::$revision, iTunes\Response::$latestReceiptInfo) in addition toEnvironment::fromString() now trims input and accepts 'prod' as an alias for production.psr/http-client ^1.0, psr/http-factory ^1.0, psr/http-message ^2.0, psr/log ^3.0 and guzzlehttp/psr7 ^2.6; guzzlehttp/guzzle allows ^7.9|^8.0;minimum-stability changed from dev to stable.Carbon\CarbonInterface|null (backed by UTCCarbonImmutable) instead of Carbon\Carbon|null. Affected methods:AppleAppStore\Transaction::getPurchaseDate(), getOriginalPurchaseDate(), getExpiresDate(),getSignedDate(), getRevocationDate(); AppleAppStore\RenewalInfo::getExpirationIntentDate(),getGracePeriodExpiresDate(), getSignedDate(), getRecentSubscriptionStartDate(),getRenewalDate(); AppleAppStore\ServerNotification::getSignedDate() (non-nullCarbonInterface); iTunes\Transaction::getPurchaseDate(), getOriginalPurchaseDate(),getExpiresDate(), getCancellationDate(); iTunes\Response::getOriginalPurchaseDate(),getRequestDate(), getReceiptCreationDate(); iTunes\RenewalInfo::getGracePeriodExpiresDate();iTunes\ServerNotification::getAutoRenewStatusChangeDate();Amazon\Transaction::getPurchaseDate() (now nullable; was non-null Carbon),getCancellationDate(), getRenewalDate(), getGracePeriodEndDate(), getFreeTrialEndDate().AbstractTransaction is abstract readonly; AppleAppStore\Transaction, iTunes\TransactionAmazon\Transaction are final readonly; AppleAppStore\Response, iTunes\Response,Amazon\Response, iTunes\RenewalInfo and Amazon\Validator are final and can no longer beAbstractResponse::__construct(array $data = [], Environment $environment = Environment::PRODUCTION) no longer accepts null;AppleAppStore\Response::__construct(array $data = []) derives the environment from the payload'senvironment field; AbstractTransaction::__construct(array $rawData = [], int $quantity = 1, ?string $productId = null, ?string $transactionId = null) with quantity defaulting to 1 (was0). (#200)bool:AppleAppStore\RenewalInfo::getAutoRenewStatus(), isInBillingRetryPeriod(), isUpgraded();AppleAppStore\Response::hasMore(); iTunes\Transaction::isTrialPeriod(),isInIntroOfferPeriod(); Amazon\Transaction::isAutoRenewing();iTunes\RenewalInfo::isInBillingRetryPeriod() (was ?int) and getStatus() (now non-nullstring). (#200)AbstractTransaction::getProductId() andgetTransactionId() return ?string (were string); getRawData() on responses andarray; iTunes\Transaction::getOriginalTransactionId(),iTunes\RenewalInfo::getAutoRenewProductId() and getOriginalTransactionId() return ?string.APIError classes are now backed enums (Amazon\APIError: string,AppleAppStore\APIError: int, iTunes\APIError: int): former constants are enum cases (use->value for the raw code) and the static messages() arrays were replaced by message().Environment is now a string-backed enum (SANDBOX = 'sandbox', PRODUCTION = 'production'), so Environment::from()/tryFrom() and ->value are available.AbstractValidator::$client is protected (was public);Amazon\Validator::__construct() now defaults $environment to Environment::PRODUCTION.AppleAppStore\ServerNotification throws ValidationException("Unknown notificationType: ...")ValueError escape.AppleAppStore\JWT\TokenGenerator::EXPIRATION_MINUTES reduced from 60 to 20, so generated AppTokenGenerator, TokenVerifier andReceiptUtility are now final; TokenGeneratorConfig, TokenIssuer and TokenKey are final readonly. (#200)CarbonInterface return-type change.ArrayAccess (offsetGet(), offsetSet(), offsetUnset(), offsetExists())AppleAppStore\Transaction, AppleAppStore\Response, AppleAppStore\RenewalInfo,iTunes\Transaction, iTunes\RenewalInfo and Amazon\Transaction.parse() on all Response and Transaction classes and parseData() on bothRenewalInfo classes were removed; parsing happens in the constructor.AbstractResponse::setEnvironment(),AbstractTransaction::setQuantity(), setProductId(), setTransactionId(),AppleAppStore\Transaction::setEnvironment() and Amazon\Validator::setDeveloperSecret().AppleAppStore\Response::getSignedTransactions() removed (the raw JWS stringsgetRawData()['signedTransactions']).AppleAppStore\Transaction::getIsUpgraded(): ?bool replaced by isUpgraded(): bool. (#200)iTunes\RenewalInfo constants RETRY_PERIOD_ACTIVE, RETRY_PERIOD_INACTIVE,AUTO_RENEW_ACTIVE and AUTO_RENEW_INACTIVE removed (the getters now return bool).Notes written from the code diff for this release; see CHANGELOG.md.
Version 7.0.0 of the Google Play Billing Library and Kotlin extensions are now available.
Added APIs to support installment subscriptions.
Added ProductDetails.InstallmentPlanDetails for installment base plans that users are eligible to purchase. This API helps your app identify the installment plan and its commitment setup to provide related information to the user. To learn more, see our subscription installments guide .
Added PendingPurchasesParams and BillingClient.Builder.enablePendingPurchases(PendingPurchaseParams) to replace BillingClient.Builder.enablePendingPurchases() , which has been deprecated in this release.
The deprecated enablePendingPurchases() is functionally equivalent to enablePendingPurchases(PendingPurchasesParams.newBuilder().enableOneTimeProducts().build()) .
Added APIs to support pending transactions for subscription prepaid plans:
Use PendingPurchasesParams.Builder.enablePrepaidPlans() along with BillingClient.Builder.enablePendingPurchases(PendingPurchaseParams) to enable pending transactions for subscription prepaid plans. When adding support, be sure that your app also correctly manages subscription lifecycles. To learn more see our pending purchases guide .
Added Purchase.PendingPurchaseUpdate and Purchase.getPendingPurchaseUpdate() for retrieving the pending top-up or upgrade or downgrade to an existing subscription.
Removed BillingClient.Builder.enableAlternativeBilling() , AlternativeBillingListener , and AlternativeChoiceDetails .
Developers should use BillingClient.Builder.enableUserChoiceBilling() with UserChoiceBillingListener and UserChoiceDetails in the listener callback instead.
Removed BillingFlowParams.ProrationMode , BillingFlowParams.SubscriptionUpdateParams.Builder.setReplaceProrationMode() , and BillingFlowParams.SubscriptionUpdateParams.Builder.setReplaceSkusProrationMode() .
Developers should use BillingFlowParams.SubscriptionUpdateParams.ReplacementMode with BillingFlowParams.SubscriptionUpdateParams.Builder#setSubscriptionReplacementMode(int) instead.
Removed BillingFlowParams.SubscriptionUpdateParams.Builder#setOldSkuPurchaseToken() .
Developers should use BillingFlowParams.SubscriptionUpdateParams.Builder#setOldPurchaseToken(java.lang.String) instead.
BillingClient.queryPurchaseHistoryAsync() has been deprecated and will be removed in a future release. Developers should use the following alternatives instead:
Acknowledged and pending purchases: Use BillingClient.queryPurchasesAsync() to fetch the active purchases.
Consumed purchases: Developers should keep track of consumed purchases on their own servers.
Canceled purchases: Use the voided-purchases developer API.
For more details, see Query Purchase History
BillingFlowParams.ProductDetailsParams.setOfferToken() now throws an exception when developers specify an empty offerToken .
Updated minSdkVersion to 21 and targetSdkVersion to 34.
Sandbox receipts sent to a production iTunes\Validator no longer fail. AbstractValidator::getClient() built the Guzzle client once with the first base
iTunes\Validator no longer fail.AbstractValidator::getClient() built the Guzzle client once with the first base_uri andmakeRequest(Environment::SANDBOX), the retry was still POSTed tohttps://buy.itunes.apple.com, got 21007 again and surfaced as ValidationException "iTunes APIgetClient() is called with a different baseprotected ?string $baseUri), so the retry really goes tohttps://sandbox.itunes.apple.com. The same fix makes setEnvironment() take effect on anyNotes written from the code diff for this release; see CHANGELOG.md.
AppleAppStore\Validator now passes the relative path (for example /inApps/v2/history/{id} ) to Guzzle and relies on the client's base_uri , instead of
AppleAppStore\Validator now passes the relative path (for example /inApps/v2/history/{id}) tobase_uri, instead of concatenating the endpoint into anNotes written from the code diff for this release; see CHANGELOG.md.
AppleAppStore\Validator::requestTestNotification(): string calls POST /inApps/v1/notifications/test on the App Store Server API and returns the testNo
AppleAppStore\Validator::requestTestNotification(): string calls POST /inApps/v1/notifications/test on the App Store Server API and returns thetestNotificationToken; throws ValidationException if the token is missing from the response.AppleAppStore\APIError: a final class of int constants for App Store Server API error codes,GENERAL_BAD_REQUEST (4000000) through INVALID_TRANSACTION_ID (4000006),TRANSACTION_ID_NOT_FOUND (4040010), RATE_LIMIT_EXCEEDED (4290000) toGENERAL_INTERNAL_RETRYABLE (5000001). Constants only; there is no messages() helper on thisiTunes\APIError: a final class with int constants for the verifyReceipt status codesVALID = 0, JSON_INVALID = 21000, RECEIPT_DATA_MALFORMED = 21002,RECEIPT_AUTHENTICATION_FAILED = 21003, SHARED_SECRET_INVALID = 21004, SERVER_UNAVAILABLE =SUBSCRIPTION_EXPIRED = 21006, SANDBOX_RECEIPT_ON_PRODUCTION = 21007,PRODUCTION_RECEIPT_ON_SANDBOX = 21008, INTERNAL_DATA_ACCESS_ERROR = 21009,USER_ACCOUNT_NOT_FOUND = 21010, INTERNAL_ERROR = 21100) and a static messages(): array mapAmazon\APIError: a final class with string constants for Amazon RVS error namesINVALID_RECEIPT_ID, INVALID_USER_ID, INVALID_DEVELOPER_SECRET, INVALID_JSON,INTERNAL_ERROR) and a static messages(): array.iTunes\Validator now throws ValidationException with the message iTunes API error [<status>]: <description> and with getCode() set to Apple's status code; previously the message was theAppleAppStore\Validator error handling: on a non-200 response the exception message is App Store API error [<code>]: <message> where the code is the body's errorCode (falling back to thegetCode(); HTTP 401 and 404 produce the fixed messagesmakeRequest() into validate(); makeRequest() is now a generic protected function makeRequest(string $method, string $uri = '', array $queryParams = []): Response.Amazon\Validator error handling: on a non-200 response the exception message is Amazon API error [<http status>]: <description> with getCode() set to the HTTP status. The description ismessage field when present, otherwise a generic text. Note thatAmazon\APIError::messages() is keyed by the integer HTTP status while that mapiTunes\Validator::RESULT_* constants (RESULT_OK, RESULT_VALID_NO_PURCHASE,RESULT_APPSTORE_CANNOT_READ, RESULT_DATA_MALFORMED, RESULT_RECEIPT_NOT_AUTHENTICATED,RESULT_SHARED_SECRET_NOT_MATCH, RESULT_RECEIPT_SERVER_UNAVAILABLE,RESULT_RECEIPT_VALID_BUT_SUB_EXPIRED, RESULT_SANDBOX_RECEIPT_SENT_TO_PRODUCTION,RESULT_PRODUCTION_RECEIPT_SENT_TO_SANDBOX, RESULT_RECEIPT_WITHOUT_PURCHASE) were removed iniTunes\APIError.Amazon\Validator::RESULT_INVALID_RECEIPT, RESULT_INVALID_DEVELOPER_SECRET,RESULT_INVALID_USER_ID and RESULT_INTERNAL_ERROR were removed; Amazon\APIError holds stringprotected function makeRequest(): mixed declaration was dropped fromAbstractValidator; each validator now declares its own makeRequest() signature. Only relevantAbstractValidator.Notes written from the code diff for this release; see CHANGELOG.md.
Version 6.1.0 of the Google Play Billing Library and Kotlin extensions are now available.
Added APIs to support alternative billing only (i.e. without user choice)
Added BillingClient.Builder.enableAlternativeBillingOnly() to functionally enable the ability to offer alternative billing only.
Added BillingClient.isAlternativeBillingOnlyAvailableAsync() to check the availability of offering alternative billing only.
Added BillingClient.showAlternativeBillingOnlyInformationDialog() to show an information dialog to inform users when alternative billing only is being used.
Added BillingClient.createAlternativeBillingOnlyReportingDetailsAsync() to create a payload required to report transactions made through alternative billing only.
Updated the user choice billing APIs
Added UserChoiceBillingListener to replace AlternativeBillingListener which has been marked as deprecated.
Added UserChoiceDetails to replace AlternativeChoiceDetails which has been marked as deprecated.
Added BillingClient.Builder.enableUserChoiceBilling() to replace BillingClient.Builder.enableAlternativeBilling() which has been marked as deprecated.
Added BillingClient.getBillingConfigAsync() to retrieve Google Play country.
Environment::fromString(string $value): Environment parses 'sandbox' / 'production' case-insensitively and throws InvalidArgumentException for anythin
Environment::fromString(string $value): Environment parses 'sandbox' / 'production'InvalidArgumentException for anything else.AppleAppStore\Response::getTransactions() and Amazon\Response::getTransactions() are now@return array<Transaction> so static analysers and IDEsAppleAppStore\JWT\TokenGenerator::generate() throws ValidationException ("Issuer ID must notiss, and TokenGenerator::decodeToken('')ValidationException ("Cannot parse empty JWT payload") instead of a parser error.AppleAppStore\Validator throws ValidationException ("JWT generation failed: Cannot generate aiTunes\Validator throws ValidationException ("Unable to encode data to iTunes server") if thephpstan/phpstan widened from ^1.11 to ^1.11 || ^2.0; theextra.google/apiclient-services entry from the removed Google Play integration wasNotes written from the code diff for this release; see CHANGELOG.md.
AppleAppStore\Validator::validate() now sends ?sort=DESCENDING on the GET /inApps/v2/history/{transactionId} request, so Response::getTransactions() r
AppleAppStore\Validator::validate() now sends ?sort=DESCENDING on the GET /inApps/v2/history/{transactionId} request, so Response::getTransactions() returns the newest
transactions first (the API default is ascending).AppleAppStore\ServerNotificationSubtype could not be autoloaded in 6.0.0: the enum declared namespace ReceiptValidator\AppleAppStore but lived at src/
AppleAppStore\ServerNotificationSubtype could not be autoloaded in 6.0.0: the enum declared
namespace ReceiptValidator\AppleAppStore but lived at
src/AppleAppStore/JWT/ServerNotificationSubtype.php, so any App Store Server Notification V2
carrying a subtype failed with a class-not-found error inside
ServerNotification::__construct(). The file now lives at
src/AppleAppStore/ServerNotificationSubtype.php; the fully qualified name is unchanged.AppleAppStore\Transaction now extends AbstractTransaction instead of AbstractResponse. As a
result getTransactionId(), getProductId() and getQuantity() are inherited and now return
non-nullable string, string and int (previously ?string, ?string, ?int), the
setTransactionId(), setProductId() and setQuantity() setters became available, and the class
no longer exposes AbstractResponse::getTransactions(). getEnvironment() / setEnvironment()
are kept.New ReceiptValidator\AppleAppStore namespace for Apple's App Store Server API and App Store Server Notifications V2 (merged through #192 and #193):
ReceiptValidator\AppleAppStore namespace for Apple's App Store Server API and App Store
Server Notifications V2 (merged through
#192 and
#193):
AppleAppStore\Validator with __construct(string $signingKey, string $keyId, string $issuerId, string $bundleId, Environment $environment = Environment::PRODUCTION), setTransactionId(string $transactionId): self and validate(?string $transactionId = null): AppleAppStore\Response. It
signs an ES256 JWT with your App Store Connect API key and calls GET /inApps/v2/history/{transactionId} on ENDPOINT_PRODUCTION
(https://api.storekit.itunes.apple.com) or ENDPOINT_SANDBOX
(https://api.storekit-sandbox.itunes.apple.com); a missing transaction ID, transport failure,
non-200 status or JWT generation failure throws ValidationException.AppleAppStore\Response (ArrayAccess) with getRevision(), getBundleId(),
getAppAppleId(), hasMore(), getSignedTransactions() plus the inherited
getTransactions(), getRawData() and getEnvironment(). Each entry of signedTransactions
is decoded and its signature and x5c certificate chain verified before it becomes a
Transaction.AppleAppStore\Transaction (ArrayAccess) exposing the decoded JWS transaction payload:
getOriginalTransactionId(), getTransactionId(), getWebOrderLineItemId(), getBundleId(),
getProductId(), getSubscriptionGroupIdentifier(), getPurchaseDate(),
getOriginalPurchaseDate(), getExpiresDate(), getQuantity(), getType(),
getAppAccountToken(), getInAppOwnershipType(), getSignedDate(), getRevocationReason(),
getRevocationDate(), getIsUpgraded(), getOfferType(), getOfferIdentifier(),
getStorefront(), getStorefrontId(), getTransactionReason(), getCurrency(), getPrice(),
getOfferDiscountType(), getAppTransactionId(), getOfferPeriod().AppleAppStore\RenewalInfo (ArrayAccess) exposing the decoded JWS renewal-info payload:
getAutoRenewProductId(), getAutoRenewStatus(), getExpirationIntentDate(),
isInBillingRetryPeriod(), isUpgraded(), getOriginalTransactionId(),
getPriceConsentStatus(), getGracePeriodExpiresDate(), getRenewalPrice(), getCurrency(),
getOfferIdentifier(), getOfferType(), getOfferDiscountType(), getOfferPeriod(),
getAppTransactionId(), getAppAccountToken(), getEligibleWinBackOfferIds(),
getSignedDate(), getRecentSubscriptionStartDate(), getRenewalDate().AppleAppStore\ServerNotification for Server Notifications V2: __construct(array $data) takes
the decoded request body, requires a signedPayload key, verifies its signature (throwing
ValidationException otherwise) and exposes getNotificationType(): ServerNotificationType,
getSubtype(): ?ServerNotificationSubtype, getNotificationUUID(), getSignedDate(),
getBundleId(), getEnvironment(), getTransaction(): ?Transaction and getRenewalInfo(): ?RenewalInfo.AppleAppStore\ServerNotificationType (SUBSCRIBED,
DID_CHANGE_RENEWAL_PREF, DID_CHANGE_RENEWAL_STATUS, OFFER_REDEEMED, DID_RENEW,
EXPIRED, DID_FAIL_TO_RENEW, GRACE_PERIOD_EXPIRED, PRICE_INCREASE, REFUND,
REFUND_DECLINED, CONSUMPTION_REQUEST, RENEWAL_EXTENDED, REVOKE, TEST,
RENEWAL_EXTENSION, REFUND_REVERSED, EXTERNAL_PURCHASE_TOKEN, ONE_TIME_CHARGE) and
AppleAppStore\ServerNotificationSubtype (INITIAL_BUY, RESUBSCRIBE, DOWNGRADE, UPGRADE,
AUTO_RENEW_ENABLED, AUTO_RENEW_DISABLED, VOLUNTARY, BILLING_RETRY, PRICE_INCREASE,
GRACE_PERIOD, PENDING, ACCEPTED, BILLING_RECOVERY, PRODUCT_NOT_FOR_SALE, SUMMARY,
FAILURE, UNREPORTED). In this release the subtype enum's file was misplaced under JWT/ and
could not be autoloaded; see 6.0.2.AppleAppStore\ReceiptUtility with static extractTransactionIdFromAppReceipt(string $appReceipt): ?string and extractTransactionIdFromTransactionReceipt(string $transactionReceipt): ?string, for pulling a transaction ID out of a legacy receipt so it can
be looked up through the new API.AppleAppStore\JWT\TokenGenerator (generate(): Token, static decodeToken(string $signedPayload): Token), TokenGeneratorConfig (forAppStore(TokenIssuer, ?Clock),
config(), issuer(), clock()), TokenIssuer (id(), bundle(), key(), signer()),
TokenKey (kid(), contents(), passphrase()) and TokenVerifier::verify(Token): bool,
which requires alg ES256, a three-certificate x5c chain whose intermediate and root SHA-1
fingerprints match Apple's, a valid chain, and a signature that verifies against the leaf
certificate.iTunes\ServerNotification for the legacy (V1) server-to-server notifications: __construct(array $data, ?string $sharedSecret = null) throws ValidationException unless the payload's password
equals the shared secret, then exposes getNotificationType(): iTunes\ServerNotificationType,
getEnvironment(), getAutoRenewProductId(), getAutoRenewStatus(),
getAutoRenewStatusChangeDate(), getBundleId(), getBvrs(), getOriginalTransactionId(),
getPassword(), getLatestReceipt(): iTunes\Response and getPendingRenewalInfo(): ?iTunes\RenewalInfo. String-backed enum iTunes\ServerNotificationType (CANCEL,
CONSUMPTION_REQUEST, DID_CHANGE_RENEWAL_PREF, DID_CHANGE_RENEWAL_STATUS,
DID_FAIL_TO_RENEW, DID_RECOVER, DID_RENEW, INITIAL_BUY, INTERACTIVE_RENEWAL,
PRICE_INCREASE_CONSENT, REFUND, REVOKE, TEST).ReceiptValidator\Environment enum (SANDBOX, PRODUCTION), used by every validator, response
and notification instead of endpoint strings.ReceiptValidator\Exceptions\ValidationException, the single exception type thrown by all
validators and parsers.AbstractValidator (getEnvironment(), setEnvironment(), abstract
validate(), and a public $client property for injecting a Guzzle client), AbstractResponse
(getTransactions(), getRawData(), getEnvironment(), setEnvironment(), abstract parse())
and AbstractTransaction (getRawData(), getQuantity()/setQuantity(),
getProductId()/setProductId(), getTransactionId()/setTransactionId(), abstract parse()).iTunes\Transaction::hasExpired(): bool and wasCanceled(): bool convenience checks.Amazon\Transaction::getGracePeriodEndDate(), getFreeTrialEndDate(), isAutoRenewing(),
getTerm() and getTermSku(), exposing the remaining fields of Amazon's RVS response.^8.2 to ^8.3; guzzlehttp/guzzle
narrowed from ^6.3|^7.0 to ^7.0 (Guzzle 6 dropped); nesbot/carbon narrowed from
^1.0|^2.0|^3.0 to ^2.72.6|^3.0; new requirements lcobucci/jwt ^5.5, lcobucci/clock ^3.3,
phpseclib/phpseclib ^3.0 and ext-openssl; google/apiclient ^2.10 and
google/apiclient-services ~0.249 removed.iTunes\Validator constructor is now __construct(?string $sharedSecret = null, Environment $environment = Environment::PRODUCTION) (was __construct(string $endpoint = self::ENDPOINT_PRODUCTION)), and validate() is now validate(?string $receiptData = null): iTunes\Response (was validate(?string $receipt_data = null, ?string $shared_secret = null): ResponseInterface). setReceiptData(string $receiptData = '') no longer accepts null. The
RESULT_* status constants moved from iTunes\ResponseInterface onto iTunes\Validator, with
RESULT_VALID_NO_PURCHASE (2) added.iTunes\Validator now throws ValidationException for every Apple status other
than 0 and 21006 (expired subscription) instead of returning a response whose isValid() is
false. When running against production and Apple returns 21007 (sandbox receipt sent to
production) it transparently retries against the sandbox endpoint and returns a Response whose
getEnvironment() is Environment::SANDBOX. The exclude-old-transactions flag is no longer
sent (see Removed).iTunes\Response class
(constructor (?array $data = [], Environment $environment = Environment::PRODUCTION)). Compared
with the old AbstractResponse/ResponseInterface: getPurchases() is now getTransactions()
(returning iTunes\Transaction[]), isSandbox()/isProduction() are replaced by
getEnvironment(): Environment, parseData() is parse(), and getBundleId()/getAppItemId()
now return ?string instead of string. isRetryable(), getLatestReceipt(),
getLatestReceiptInfo(), getPendingRenewalInfo(), getOriginalPurchaseDate(),
getRequestDate(), getReceiptCreationDate() and getRawData() are kept.iTunes\PurchaseItem is renamed iTunes\Transaction (now extends
AbstractTransaction); getRawResponse() is now getRawData() and parseData() is parse().
All other getters keep their names.iTunes\PendingRenewalInfo is renamed iTunes\RenewalInfo; the getters and the
EXPIRATION_INTENT_*, RETRY_PERIOD_*, AUTO_RENEW_* and STATUS_* constants are unchanged.Amazon\Validator constructor is now __construct(string $developerSecret, Environment $environment) (was __construct(string $endpoint = self::ENDPOINT_PRODUCTION)).
ENDPOINT_SANDBOX changed from http://localhost:8080/RVSSandbox/ to
https://appstore-sdk.amazon.com/sandbox and ENDPOINT_PRODUCTION from
https://appstore-sdk.amazon.com/version/1.0/verifyReceiptId/ to
https://appstore-sdk.amazon.com (the
/version/1.0/verifyReceiptId/developer/{secret}/user/{user}/receiptId/{id} path is built
internally). validate(): Amazon\Response now throws ValidationException (code = HTTP status)
on any non-200 reply instead of returning a response with a result code.Amazon\PurchaseItem is renamed Amazon\Transaction (extends
AbstractTransaction, implements ArrayAccess); getRawResponse() is now getRawData() and
parseJsonResponse() is parse().Amazon\Response now extends AbstractResponse with constructor (?array $data = [], Environment $environment = Environment::PRODUCTION) (was (int $httpStatusCode = 200, ?array $jsonResponse = [])). getPurchases() is now getTransactions() (a single
Amazon\Transaction), parseJsonResponse() is parse(), and the receipt fields are available
through getRawData().GooglePlay\Validator, GooglePlay\Acknowledger,
GooglePlay\AbstractResponse, GooglePlay\PurchaseResponse, GooglePlay\SubscriptionResponse
and GooglePlay\SubscriptionV2Response were deleted along with the google/apiclient
dependencies.iTunes\AbstractResponse, iTunes\ProductionResponse, iTunes\SandboxResponse,
iTunes\ResponseInterface and iTunes\EnvironmentResponseInterface were removed in favour of
iTunes\Response; with them went getResultCode(), setResultCode(), isValid(),
getReceipt(), getPurchases(), isSandbox() and isProduction().iTunes\Validator::getEndpoint(), setEndpoint(), getExcludeOldTransactions(),
setExcludeOldTransactions(), getRequestOptions() and setRequestOptions() were removed; the
environment is chosen via Environment, and custom Guzzle options can only be applied by
assigning your own client to the public $client property.Amazon\Validator::getEndpoint()/setEndpoint() and Amazon\Response::RESULT_OK,
RESULT_INVALID_RECEIPT, RESULT_INVALID_DEVELOPER_SECRET, RESULT_INVALID_USER_ID,
RESULT_INTERNAL_ERROR, getResultCode(), getReceipt(), getPurchases() and isValid() were
removed (the four error codes live on Amazon\Validator as RESULT_INVALID_RECEIPT,
RESULT_INVALID_DEVELOPER_SECRET, RESULT_INVALID_USER_ID, RESULT_INTERNAL_ERROR until 6.1.0).ReceiptValidator\RunTimeException was removed; all errors are now
ReceiptValidator\Exceptions\ValidationException.iTunes\PendingRenewalInfo::$grace_period_expires_date and $is_in_billing_retry_period now default to null; in 5.0.0 they were typed properties without
iTunes\PendingRenewalInfo::$grace_period_expires_date and $is_in_billing_retry_period now
default to null; in 5.0.0 they were typed properties without a default, so isInGracePeriod()
(and the related getters) threw "must not be accessed before initialization" whenever Apple's
pending_renewal_info entry lacked grace_period_expires_date_ms or
is_in_billing_retry_period. Thanks to @mpoiriert
(#191)Breaking: Removed the Windows Store validator: ReceiptValidator\WindowsStore\Validator and ReceiptValidator\WindowsStore\CacheInterface are gone, and
ReceiptValidator\WindowsStore\Validator and
ReceiptValidator\WindowsStore\CacheInterface are gone, and the robrichards/xmlseclibs
dependency was dropped with them.^7.3|^8.0 to ^8.2.Amazon\PurchaseItem::__construct(?array $jsonResponse),
Amazon\Response::parseJsonResponse(?array $jsonResponse),
iTunes\AbstractResponse::__construct(?array $data), iTunes\PurchaseItem::__construct(?array $data), iTunes\PendingRenewalInfo::__construct(?array $data). Passing a non-array (e.g. a
string) to these now throws TypeError instead of RuntimeException('Response must be a scalar value').Amazon\Response::__construct(int $httpStatusCode = 200, ?array $jsonResponse = [])
— the second parameter's default changed from null to [], so new Response(200) now parses an
empty array and getPurchases() returns one empty PurchaseItem instead of an empty array; pass
null explicitly to keep the old behaviour.static return types added across
GooglePlay\Validator: __construct(AndroidPublisher $service, bool $validationModePurchase = true), setPackageName(string), setPurchaseToken(string), setProductId(string),
setValidationModePurchase(bool), setValidationSubscriptionV2(bool); validatePurchase(): PurchaseResponse, validateSubscription(): SubscriptionResponse, validateSubscriptionV2(): SubscriptionV2Response, getPublisherService(): ?AndroidPublisher.GooglePlay\Acknowledger::__construct() now requires string for $packageName,
$productId, $purchaseToken and $strategy; acknowledge() is declared : bool.TypeError instead of returning null:
AbstractResponse::getDeveloperPayload(): array|string, getAcknowledgementState(): int,
getKind(): string; PurchaseResponse::getConsumptionState(): int, getPurchaseTimeMillis(): string, getDeveloperPayloadElement(string $key): string, getPurchaseState(): string;
SubscriptionResponse::getAutoRenewing(): bool, getCancelReason(): ?int, getCountryCode(): string, getPriceAmountMicros(): string, getPriceCurrencyCode(): string,
getStartTimeMillis(): string, getExpiryTimeMillis(): int, getUserCancellationTimeMillis(): ?int, getPaymentState(): int, getExpiresDate(): string, getExternalAccountId(): string.GooglePlay\SubscriptionResponse::getPriceAmountMicros() is now documented and typed as string
(Google returns the int64 as a string); it was previously documented as int.iTunes\PurchaseItem and iTunes\PendingRenewalInfo ArrayAccess methods now carry native
return types (offsetSet(): void, offsetGet(): mixed, offsetUnset(): void, offsetExists(): bool); subclasses overriding them must match.Amazon\*, GooglePlay\*, iTunes\*); subclasses that
redeclare these protected properties must use compatible types.composer.json: phpunit/phpunit dev dependency raised to ^11.0; minimum-stability: dev with
prefer-stable: true added (affects the repository only, not consumers).Amazon\Response default change is a behaviour change.iTunes\Validator now honours a base_uri key passed via setRequestOptions(); previously getClientConfig() always overwrote it with the selected Apple e
iTunes\Validator now honours a base_uri key passed via setRequestOptions(); previously
getClientConfig() always overwrote it with the selected Apple endpoint. This lets you point the
validator at a mock server. Thanks to @aliozkan (Teknasyon-Teknoloji)
(#181)composer.json: nesbot/carbon constraint widened from ^1.0|^2.0 to ^1.0|^2.0|^3.0. Thanks to @gjuric
composer.json: nesbot/carbon constraint widened from ^1.0|^2.0 to ^1.0|^2.0|^3.0. Thanks
to @gjuric (#179)php constraint in
composer.json is unchanged (^7.3|^8.0.0, which already allowed every 8.x).GooglePlay\Validator::setValidationSubscriptionV2() now sets the validationSubscriptionV2 flag; in 4.4.0 it mistakenly wrote to validationModePurchase
GooglePlay\Validator::setValidationSubscriptionV2() now sets the validationSubscriptionV2
flag; in 4.4.0 it mistakenly wrote to validationModePurchase, so
setValidationSubscriptionV2(true) actually switched the validator into purchase mode. Thanks to
@yankers (#172)GooglePlay\Validator::validate() now checks the validationSubscriptionV2 flag; in 4.4.0 the
elseif condition called validateSubscriptionV2() itself, so any non-purchase validation
performed a Subscriptions V2 API call (twice when it succeeded) regardless of the flag.
(#172)GooglePlay\Validator::validateSubscriptionV2() and GooglePlay\Validator::setValidationSubscriptionV2(bool) call Google's purchases.subscriptionsv2.get
GooglePlay\Validator::validateSubscriptionV2() and
GooglePlay\Validator::setValidationSubscriptionV2(bool) call Google's
purchases.subscriptionsv2.get endpoint; the result is wrapped in the new
GooglePlay\SubscriptionV2Response class, which exposes the raw
Google\Service\AndroidPublisher\SubscriptionPurchaseV2 via getRawResponse() and adds no
V2-specific getters of its own. Thanks to @yankers
(#171)composer.json: new explicit dependency google/apiclient-services: ~0.249 (needed for the
SubscriptionPurchaseV2 model and the purchases_subscriptionsv2 resource).setValidationSubscriptionV2() and validate() were wired incorrectly
(see 4.4.1), so the V2 flow only works correctly from 4.4.1 onward; calling
validateSubscriptionV2() directly worked in 4.4.0.…null to parameter #1 ($json) of type string is deprecated" notice for purchases with no developer payload. Thanks to @sica07
GooglePlay\SubscriptionResponse::getExternalAccountId() returns the subscription's
externalAccountId. Thanks to @marijap93
(#165)getExternalAccounId() and the commit subject
says addExternalAccountId; the method actually added is getExternalAccountId().GooglePlay\PurchaseResponse::__construct() only calls json_decode() on developerPayload when
the field is set, avoiding the PHP 8.1 "passing null to parameter #1 ($json) of type string is
deprecated" notice for purchases with no developer payload. Thanks to @sica07
(#168)…the PHP 8.1 "Return type should be compatible" deprecation notices. Thanks to @srjlewis (#154, #155)
Breaking: Minimum PHP version raised from ^7.2.5 to ^7.3 (PHP 7.2 is end of life).
^7.2.5 to ^7.3 (PHP 7.2 is end of life).
(#144)google/apiclient constraint raised from ^2.0 to ^2.10.
(#153)GooglePlay\Validator::__construct() and GooglePlay\Acknowledger::__construct() now type-hint
Google\Service\AndroidPublisher instead of Google_Service_AndroidPublisher, and the
acknowledge requests use Google\Service\AndroidPublisher\ProductPurchasesAcknowledgeRequest /
SubscriptionPurchasesAcknowledgeRequest. The legacy names remain class aliases in
google/apiclient-services >= 0.200, so existing code constructing
Google_Service_AndroidPublisher keeps working. Thanks to @yyeltsyn
(#153)Amazon\Response::getReceipt(): array,
Amazon\Response::getPurchases(): array, Amazon\Validator::validate(): Response,
iTunes\AbstractResponse::getPurchases(): array, getLatestReceiptInfo(): array,
getPendingRenewalInfo(): array (and the same three on iTunes\ResponseInterface, so custom
implementations must declare them), iTunes\PendingRenewalInfo::isInGracePeriod(): bool and
offsetExists($key): bool.composer.json: phpunit/phpunit dev dependency raised from ^8.0 to ^9.0.google/apiclient:^2.10 requirement; the PHP 7.2 drop and the new return types were not listed.iTunes\Validator::setEndpoint() now validates the endpoint and throws \InvalidArgumentException for anything other than ENDPOINT_PRODUCTION or ENDPOIN
iTunes\Validator::setEndpoint() now validates the endpoint and throws
\InvalidArgumentException for anything other than ENDPOINT_PRODUCTION or ENDPOINT_SANDBOX;
previously only the constructor validated it, so an invalid endpoint could be set after
construction. The constructor now delegates to setEndpoint(). Thanks to @pwellingelastique
(#142)PHP 8.0 support: composer php constraint widened from ^7.2.5 to ^7.2.5|^8.0.0. No source changes were needed. Thanks to @mewm
php constraint widened from ^7.2.5 to ^7.2.5|^8.0.0. No source
changes were needed. Thanks to @mewm
(#141)google/apiclient-services dependency via
Google_Task_Composer::cleanup (documentation only). Thanks to @passions-app
(#129)Amazon\Validator::validate() requested a leading-slash path (/developer/...), which made Guzzle discard the /version/1.0/verifyReceiptId/ path of the
Amazon\Validator::validate() requested a leading-slash path (/developer/...), which made
Guzzle discard the /version/1.0/verifyReceiptId/ path of the base URI, so every Amazon request
in 4.0.0 went to the wrong URL. The trailing slash was restored on
Amazon\Validator::ENDPOINT_PRODUCTION and ENDPOINT_SANDBOX and the request path is relative
again. Thanks to @calbro7 (#126)Breaking: Minimum PHP version raised from ^7.1 to ^7.2.5. Thanks to @leemcd56
^7.1 to ^7.2.5. Thanks to @leemcd56
(#125)guzzlehttp/guzzle constraint changed from ^6.3 to ^6.3|^7.0.
(#125)iTunes\Validator::ENDPOINT_PRODUCTION and ENDPOINT_SANDBOX no longer include the
/verifyReceipt path (now https://buy.itunes.apple.com and https://sandbox.itunes.apple.com);
the path is passed on the POST request instead (needed because Guzzle 7 rejects a null request
URI). Code that compares getEndpoint() against the old full URLs or passes the full URL to the
constructor will break.iTunes\Validator::setReceiptData() and setSharedSecret() now declare ?string
parameter types.Amazon\Validator::setUserId(), setReceiptId() and setDeveloperSecret() now
declare ?string parameter types, and getDeveloperSecret() declares a ?string return type.Amazon\PurchaseItem getters gained return types: getRawResponse(): ?array,
getQuantity(): int, getProductId(): string, getTransactionId(): string, getPurchaseDate(): Carbon, getCancellationDate(): ?Carbon, getRenewalDate(): ?Carbon.Amazon\Validator, Amazon\Response and
Amazon\PurchaseItem lost their leading underscore (e.g. $_endpoint is now $endpoint,
$_response is now $raw_data); subclasses referencing the old names must be updated. The unused
Amazon\Validator::$_product_id property was removed.^7.2.5. They did not mention the endpoint-constant or type-hint changes.GooglePlay\Acknowledger constructor accepts a fifth $strategy argument with new constants ACKNOWLEDGE_STRATEGY_EXPLICIT (default; calls acknowledge un
GooglePlay\Acknowledger constructor accepts a fifth $strategy argument with new constants
ACKNOWLEDGE_STRATEGY_EXPLICIT (default; calls acknowledge unconditionally, as before) and
ACKNOWLEDGE_STRATEGY_IMPLICIT (first fetches the purchase and only acknowledges if
acknowledgementState is not already done). An unknown strategy throws
ReceiptValidator\RunTimeException. Thanks to @passions-app
(#110)robrichards/xmlseclibs constraint tightened from ^2.0|^3.0 to ^3.0.4 (drops
xmlseclibs 2.x) to clear security-advisory warnings. Other constraints were rewritten from ~ to
^ form (php: ^7.1, guzzlehttp/guzzle: ^6.3, google/apiclient: ^2.0) with no effective
change.GooglePlay\Acknowledger::acknowledge() now re-throws wrapped exceptions with $e->getCode() as
the message instead of $e->getMessage(), so the original error text is lost (looks
unintentional). (#110)passions-app fork (author Florent Blaison).GooglePlay\Acknowledger::acknowledge() passed the caught exception as the second ($code) argument of \RuntimeException, which is a TypeError; it now p
GooglePlay\Acknowledger::acknowledge() passed the caught exception as the second ($code)
argument of \RuntimeException, which is a TypeError; it now passes $e->getCode() and the
previous exception correctly.New GooglePlay\Acknowledger class (__construct(\Google_Service_AndroidPublisher $service, $packageName, $productId, $purchaseToken), acknowledge(strin
GooglePlay\Acknowledger class (__construct(\Google_Service_AndroidPublisher $service, $packageName, $productId, $purchaseToken), acknowledge(string $type = self::SUBSCRIPTION, string $developerPayload = ''): bool) with constants SUBSCRIPTION and PRODUCT, for acknowledging
purchases as required by Google Play Billing Library v2. Failures are wrapped in
\RuntimeException. Thanks to @passions-app
(#108)GooglePlay\AbstractResponse::getAcknowledgementState(): int and isAcknowledged(): bool, plus
constants ACKNOWLEDGEMENT_STATE_YET_TO_BE = 0 and ACKNOWLEDGEMENT_STATE_DONE = 1, available on
both PurchaseResponse and SubscriptionResponse.
(#108)passions-app fork (author Florent Blaison).iTunes\PendingRenewalInfo::getGracePeriodExpiresDate(): ?Carbon, parsed from grace_period_expires_date_ms. Thanks to @Teknasyon-Teknoloji
iTunes\PendingRenewalInfo::getGracePeriodExpiresDate(): ?Carbon, parsed from
grace_period_expires_date_ms. Thanks to @Teknasyon-Teknoloji
(#106)iTunes\PendingRenewalInfo::isInGracePeriod(): bool, true when the subscription is in the billing
retry period and the grace period expiry is in the future.
(#106)iTunes\Validator::setRequestOptions(array $options): self and getRequestOptions(): array to
pass extra Guzzle client options (e.g. timeouts, proxies); they are merged into the client config
under the base_uri. (#106)Teknasyon-Teknoloji fork (author Harun Pekacar).iTunes\PurchaseItem::getPromotionalOfferId(): ?string, parsed from promotional_offer_id. Thanks to @Stafox
iTunes\PurchaseItem::getPromotionalOfferId(): ?string, parsed from promotional_offer_id.
Thanks to @Stafox (#98)nesbot/carbon constraint changed from ~1 to ^1.0|^2.0.iTunes\PurchaseItem::getWebOrderLineItemId() return type changed from string to ?string; it previously raised a TypeError for purchases without web_or
iTunes\PurchaseItem::getWebOrderLineItemId() return type changed from string to ?string; it
previously raised a TypeError for purchases without web_order_line_item_id (non-subscription
items). Thanks to @lancasterSano
(#95)iTunes\ResponseInterface::getLatestReceipt() and AbstractResponse::getLatestReceipt() return
type changed from string to ?string; it previously raised a TypeError when the response had
no latest_receipt. Thanks to @Stafox
(#90)iTunes\ResponseInterface (declares all response getters and the RESULT_* constants) and iTunes\EnvironmentResponseInterface (isSandbox(): bool, isProd
iTunes\ResponseInterface (declares all response getters and the RESULT_* constants) and
iTunes\EnvironmentResponseInterface (isSandbox(): bool, isProduction(): bool). Thanks to
@Stafox (#87)iTunes\ProductionResponse and iTunes\SandboxResponse, both extending the new
iTunes\AbstractResponse; Validator::validate() returns the one matching the environment that
actually answered, so a 21007 sandbox retry yields a SandboxResponse.
(#87)iTunes\ResponseInterface::isRetryable(): bool, true when Apple's response contains the
is-retryable key (status codes 21100-21199). Thanks to @amasok
(#84)iTunes\ResponseInterface::RESULT_RECEIPT_WITHOUT_PURCHASE = 21010.
(#87)iTunes\Validator::getClientConfig(): array (protected) so subclasses can override the Guzzle
client configuration; the sandbox retry client now reuses this config instead of a bare
base_uri. (#87)iTunes\Response was removed and replaced by the abstract iTunes\AbstractResponse
plus ProductionResponse/SandboxResponse. Code that instantiates new Response(...) or
type-hints Response must switch to ResponseInterface (or one of the concrete classes).
(#87)RESULT_* constants moved from iTunes\Response to iTunes\ResponseInterface
(e.g. Response::RESULT_OK is now ResponseInterface::RESULT_OK).
(#87)iTunes\Validator::validate() return type changed from Response to
ResponseInterface. (#87)iTunes\AbstractResponse::setResultCode(int $code) now returns void instead of
self, so it can no longer be chained.
(#87)isValid() on iTunes responses now also returns true for status 21006
(RESULT_RECEIPT_VALID_BUT_SUB_EXPIRED), not only for 0; code relying on isValid() to mean
"active subscription" must also check the result code.
(#87)iTunes\AbstractResponse::getRawData() now declares a ?array return type.
(#87)iTunes\PurchaseItem casts quantity to int and parses
is_trial_period/is_in_intro_offer_period with FILTER_VALIDATE_BOOLEAN instead of comparing
to the string "true". (#87)iTunes\Validator: the private encodeRequest() became protected prepareRequestData(), and the
sandbox retry is now recursive through the same request path (a non-200 status still throws
RunTimeException). (#87)iTunes\PurchaseItem::getWebOrderLineItemId() to return ?string, but the subsequent refactor in
#87 reverted it to string in the tagged 3.0.0; the nullable return only shipped in 3.1.0.iTunes\Response::getAppItemId(), getOriginalPurchaseDate(), getRequestDate() and getReceiptCreationDate() expose the app_item_id, original_purchase_da
iTunes\Response::getAppItemId(), getOriginalPurchaseDate(), getRequestDate() and
getReceiptCreationDate() expose the app_item_id, original_purchase_date_ms,
request_date_ms and receipt_creation_date_ms receipt fields (dates as Carbon).iTunes\Response::getRawData() returns the decoded JSON array the response was built from. Thanks
to @amasok (#82)iTunes\PurchaseItem::isTrialPeriod() and isInIntroOfferPeriod() read the is_trial_period and
is_in_intro_offer_period fields.iTunes\Response::getLatestReceiptInfo() and getPendingRenewalInfo() now return an empty array
instead of null when the receipt has no such data.ext-json, and guzzlehttp/guzzle ~6.3
(was ~6.2).iTunes\Response::parseJsonResponse() and
iTunes\PurchaseItem::parseJsonResponse() were renamed to parseData();
iTunes\PendingRenewalInfo gained a public parseData() as well.iTunes\PendingRenewalInfo::getIsInBillingRetryPeriod() was renamed to
isInBillingRetryPeriod(), and getAutoRenewStatus() now returns bool instead of int.iTunes\Validator::__construct() throws \InvalidArgumentException instead of
ReceiptValidator\RunTimeException for an unknown endpoint.iTunes\Response, iTunes\PurchaseItem and iTunes\PendingRenewalInfo
constructors are typed ?array $data = null and always parse; constructing one with null now
throws RunTimeException ("Response must be a scalar value") instead of producing an empty
object.iTunes\Validator::validate(?string $receipt_data = null, ?string $shared_secret = null): Response and most iTunes getters gained scalar/nullable return types (for example
getResultCode(): int, getBundleId(): string, getPurchaseDate(): ?Carbon).
getLatestReceipt(): string and getBundleId(): string are non-nullable, so a receipt lacking
those fields will raise a TypeError.Amazon\Validator::setEndpoint() is now explicitly public (it had no visibility keyword).
Thanks to @gsingh1 (#75)iTunes\PurchaseItem trial/intro-offer flags are compared against the string "true" instead of
boolval(), which returned true for the string "false". Thanks to @cumhuronat
(#77)Amazon\PurchaseItem::getRenewalDate() now uses Carbon::createFromTimestampUTC() on the
millisecond value divided by 1000; 2.1.0 passed that seconds value to createFromTimestampMs(),
yielding a date near 1970.Carbon conversions (iTunes and Amazon) cast the rounded timestamp to int
before calling Carbon.Version 2.2.0 of the Google Play Billing provides functionality that helps developers ensure purchases are attributed to the correct user. These changes replace the need to build custom solutions based on developer payload. As part of this update, the developer payload functionality has been deprecated and will be removed in a future release. For more information, including recommended alternatives, see Developer payload .
In addition to the current Java and Kotlin versions of Google Play Billing Library 2, we released a version of the library for use with Unity. Game developers using the Unity in-app purchase API can upgrade now to take advantage of all Google Play Billing Library 2 features and to make the subsequent upgrades to future versions of the Google Play Billing Library easier.
To learn more, see Use Google Play Billing with Unity .
Java Google Play Billing Library
In AcknowledgePurchaseParams , deprecated setDeveloperPayload() and getDeveloperPayload() methods.
In ConsumeParams , deprecated setDeveloperPayload() and getDeveloperPayload() methods.
In BillingFlowParams , renamed setAccountId() to setObfuscatedAccountId() , and documented length restriction of 64 characters and restriction disallowing Personally Identifiable Information (PII) in this field. setAccountId() has been marked as deprecated and will be removed in a future library version.
In BillingFlowParams , added setObfuscatedProfileId() which works similarly to setObfuscatedAccountId() . For more information, see Developer payload updates and alternatives .
In Purchase , added the getAccountIdentifiers() method to return the obfuscated account identifiers set in BillingFlowParams .
In BillingClient , the loadRewardedSku() method has been marked deprecated as part of deprecating rewarded SKUs. You can find more information about the deprecation in the Play Console Help Center .
iTunes\PendingRenewalInfo class with getProductId(), getAutoRenewProductId(), getAutoRenewStatus(), getOriginalTransactionId(), getExpirationIntent(),
iTunes\PendingRenewalInfo class with getProductId(), getAutoRenewProductId(),
getAutoRenewStatus(), getOriginalTransactionId(), getExpirationIntent(),
getIsInBillingRetryPeriod(), getStatus() (returns
STATUS_ACTIVE/STATUS_PENDING/STATUS_EXPIRED), EXPIRATION_INTENT_* constants, and
ArrayAccess to the raw fields. Thanks to @leesherwood
(#67)iTunes\PurchaseItem now implements ArrayAccess over the raw response fields; offsetSet()
re-parses the item. Thanks to @leesherwood
(#66)Amazon\Validator::getEndpoint() and setEndpoint(string $endpoint). Thanks to @gsingh1
(#69)Amazon\PurchaseItem::getRenewalDate() reads the renewalDate field as Carbon. Thanks to
@gsingh1 (#70)iTunes\Response::getLatestReceiptInfo() returns iTunes\PurchaseItem[] sorted by
purchase date descending instead of the raw latest_receipt_info arrays. Thanks to @leesherwood
(#66)iTunes\Response::getPendingRenewalInfo() returns iTunes\PendingRenewalInfo[]
instead of the raw pending_renewal_info arrays. Thanks to @leesherwood
(#67)Version 2.1.0 of the Google Play Billing library and the new Kotlin extension are now available. The Play Billing Library Kotlin extension provides idiomatic API alternatives for Kotlin consumption, featuring better null-safety and coroutines. For code examples, see Use the Google Play Billing Library .
This version contains the following changes.
iTunes\Validator::setExcludeOldTransactions(bool $exclude) and getExcludeOldTransactions(); the exclude-old-transactions flag is now always sent in th
iTunes\Validator::setExcludeOldTransactions(bool $exclude) and getExcludeOldTransactions();
the exclude-old-transactions flag is now always sent in the verifyReceipt request body (default
false). Thanks to @leesherwood
(#65)phpunit dev dependency moved to ^6.0.iTunes\Validator::__construct(string $endpoint) and
Amazon\Validator::__construct(string $endpoint) gained a string type hint, and
setEndpoint(string $endpoint) likewise, so passing null now raises a TypeError.isValid(): bool, getResultCode(): int (Amazon),
parseJsonResponse(): self, getEndpoint(): string, and self on fluent setters (setUserId,
setReceiptId, setDeveloperSecret, setReceiptData, setSharedSecret).composer now accepts robrichards/xmlseclibs ^2.0|^3.0. Thanks to @sbacelic
iTunes\Response::getPendingRenewalInfo() returns the raw pending_renewal_info array from the verifyReceipt response. Thanks to @nielsmouthaan
iTunes\Response::getPendingRenewalInfo() returns the raw pending_renewal_info array from the
verifyReceipt response. Thanks to @nielsmouthaan
(#59)Version 2.0.2 of the Google Play Billing library is now available. This release contains updates to the reference documentation and does not change library functionality.
GooglePlay\SubscriptionResponse::getExpiresDate() is deprecated in favour of getExpiryTimeMillis(); it was introduced and deprecated within this relea…
iTunes\PurchaseItem class with getQuantity(), getProductId(), getWebOrderLineItemId(),
getTransactionId(), getOriginalTransactionId(), getPurchaseDate(),
getOriginalPurchaseDate(), getExpiresDate(), getCancellationDate() (dates as Carbon) and
getRawResponse(). The raw-data accessor was originally named getData() and read an undefined
property; fixed before release. Thanks to @tehmaestro
(#41)Amazon\PurchaseItem class with getQuantity(), getProductId(), getTransactionId(),
getPurchaseDate(), getCancellationDate() and getRawResponse(), and
Amazon\Response::getPurchases() returning one such item.GooglePlay\Validator::__construct(\Google_Service_AndroidPublisher $service, $validationModePurchase = true), setValidationModePurchase(), and validate() which dispatches
to validatePurchase() or validateSubscription(). Thanks to @stanimir-kukudov
(#39)GooglePlay\Validator::getPublisherService() exposes the underlying
\Google_Service_AndroidPublisher for other calls (voided purchases, refunds, revokes). Thanks to
@Stafox (#49)GooglePlay\SubscriptionResponse::getExpiryTimeMillis(), getUserCancellationTimeMillis(),
getPaymentState(), and GooglePlay\AbstractResponse::getRawResponse(). Thanks to
@stanimir-kukudov and @Stafox (#39,
#49)iTunes\Response::getPurchases() returns iTunes\PurchaseItem[] instead of raw
in_app arrays (both iOS 7+ and legacy receipt formats).iTunes\Response::parseJsonResponse() no longer takes an argument; the JSON array
is passed to the constructor only.GooglePlay\AbstractResponse::getConsumptionState(), getDeveloperPayloadElement()
and getPurchaseState() moved to GooglePlay\PurchaseResponse; they are no longer available on
SubscriptionResponse. AbstractResponse::getDeveloperPayload() now returns the Google object's
raw payload string, while PurchaseResponse::getDeveloperPayload() keeps returning the
JSON-decoded array. (#39)GooglePlay\SubscriptionResponse getters now call the
Google_Service_AndroidPublisher_SubscriptionPurchase accessor methods, and getAutoRenewing()
is cast to bool. (#49)nesbot/carbon ~1; PHP minimum lowered back to >= 5.5 (1.5.0 had raised it to
5.6); homepage and support restored to the aporat repository (1.5.0 pointed them at a fork).
(#49)GooglePlay\SubscriptionResponse::getExpiresDate() is deprecated in favour of
getExpiryTimeMillis(); it was introduced and deprecated within this release.
(#49)Breaking: google/apiclient requirement moved from ~1.1 to ~2.0; guzzlehttp/guzzle to ~6.2; PHP minimum raised to >= 5.6. Thanks to @stanimir-kukudov
google/apiclient requirement moved from ~1.1 to ~2.0; guzzlehttp/guzzle to ~6.2;
PHP minimum raised to >= 5.6. Thanks to @stanimir-kukudov
(#35)GooglePlay\Validator::__construct() now takes a configured
\Google_Service_AndroidPublisher instead of an options array with
client_id/client_secret/refresh_token; the library no longer builds a Google_Client or
caches access tokens in the temp directory.GooglePlay\Validator::validate(), setPurchaseType() and the
TYPE_PURCHASE/TYPE_SUBSCRIPTION constants were replaced by validatePurchase() returning
GooglePlay\PurchaseResponse and validateSubscription() returning
GooglePlay\SubscriptionResponse, instead of raw Google API objects.homepage and support fields point to the contributor's fork
(stanimir-kukudov/store-receipt-validator) in this release.GooglePlay\AbstractResponse (getConsumptionState(), getDeveloperPayload(),
getDeveloperPayloadElement($key), getKind(), getPurchaseState(), CONSUMPTION_STATE_* and
PURCHASE_STATE_* constants), GooglePlay\PurchaseResponse::getPurchaseTimeMillis(), and
GooglePlay\SubscriptionResponse (getAutoRenewing(), getCancelReason(), getCountryCode(),
getPriceAmountMicros(), getPriceCurrencyCode(), getStartTimeMillis()).GooglePlay\AbstractValidator and GooglePlay\ServiceAccountValidator (added in
1.4.0) were removed; service-account auth is now done by configuring the Google_Client yourself
and passing the publisher service in.GooglePlay\ServiceAccountValidator now passes the contents of the file at p12_key_path to Google_Auth_AssertionCredentials instead of the path string,
GooglePlay\ServiceAccountValidator now passes the contents of the file at p12_key_path to
Google_Auth_AssertionCredentials instead of the path string, so service-account validation
actually authenticates. Thanks to @ball00n-
(#29)GooglePlay\ServiceAccountValidator authenticates with a Google service account (client_email and p12_key_path options) instead of OAuth client credent
GooglePlay\ServiceAccountValidator authenticates with a Google service account (client_email
and p12_key_path options) instead of OAuth client credentials.GooglePlay\AbstractValidator extracted as the shared base for GooglePlay\Validator and
ServiceAccountValidator, holding the setters, TYPE_* constants and validate().Autoloading switched from PSR-0 to PSR-4 and source files moved from src/ReceiptValidator/ to src/; class names and the ReceiptValidator\ namespace ar
src/ReceiptValidator/ to
src/; class names and the ReceiptValidator\ namespace are unchanged.iTunes\Response::getLatestReceipt() returns null instead of an empty array when the response
has no latest_receipt; docblock return types for getBundleId(), getLatestReceipt() and
getLatestReceiptInfo() corrected. Thanks to @kernio
(#26)Breaking: composer now requires PHP >= 5.5 (was 5.3) and guzzlehttp/guzzle ~6.1 instead of guzzle/guzzle ~3.8; the satooshi/php-coveralls dev dependen
guzzlehttp/guzzle ~6.1 instead of
guzzle/guzzle ~3.8; the satooshi/php-coveralls dev dependency was dropped.iTunes\Validator::getIStoreSharedSecret() and setIStoreSharedSecret() renamed to
getSharedSecret() and setSharedSecret(); the second parameter of validate($receiptData, $sharedSecret) was renamed accordingly.Amazon\Validator::validate() now catches Guzzle RequestException: a non-2xx reply is wrapped
in Amazon\Response with its status code and body, and a failure with no HTTP response returns a
Response with RESULT_INVALID_RECEIPT.Amazon\Validator::getDeveloperSecret().iTunes\Validator::validate() now verifies Apple's TLS certificate: the 'verify' => false Guzzle option was removed from both the primary request and t
iTunes\Validator::validate() now verifies Apple's TLS certificate: the 'verify' => false
Guzzle option was removed from both the primary request and the automatic sandbox retry.
Certificate verification had been disabled since 1.0.0, leaving receipt validation open to
man-in-the-middle attacks. Thanks to @lstrojny
(#21)Version 1.2.1 of the Google Play Billing library is now available. This version contains the following changes.
Added public constructors for PurchasesResult and SkuDetailsResult to make testing easier.
SkuDetails objects can use a new method, getOriginalJson() .
All AIDL service calls are now handled by background threads.
New WindowsStore\Validator class with __construct(CacheInterface $cache = null) and validate($receipt), which returns bool. It parses the receipt XML,
WindowsStore\Validator class with __construct(CacheInterface $cache = null) and
validate($receipt), which returns bool. It parses the receipt XML, downloads the Microsoft
signing certificate named by the receipt's CertificateId from go.microsoft.com, and verifies
the XML signature with robrichards/xmlseclibs; it throws ReceiptValidator\RunTimeException on
invalid XML, a missing CertificateId, or a missing signature/key. Thanks to @MaartenStaa
(#20)WindowsStore\CacheInterface (get($key), put($key, $value, $minutes)) so callers can
cache downloaded certificates; when a cache is supplied, certificates are stored for 3600 minutes
under store-receipt-validate.windowsstore.<CertificateId>.
(#20)robrichards/xmlseclibs: ^2.0.
(#20)composer: minimum PHP lowered from >=5.4 to >=5.3; the short array syntax in Amazon\Response, GooglePlay\Validator and iTunes\Response was replaced wi
>=5.4 to >=5.3; the short array syntax in
Amazon\Response, GooglePlay\Validator and iTunes\Response was replaced with array() so the
library actually runs on 5.3. Thanks to @AlexeyKupershtokh
(#17)New Amazon\Validator class for Amazon's Receipt Verification Service: constants ENDPOINT_PRODUCTION (https://appstore-sdk.amazon.com/version/1.0/verif
Amazon\Validator class for Amazon's Receipt Verification Service: constants
ENDPOINT_PRODUCTION (https://appstore-sdk.amazon.com/version/1.0/verifyReceiptId/) and
ENDPOINT_SANDBOX (http://localhost:8080/RVSSandbox/); __construct($endpoint = self::ENDPOINT_PRODUCTION) throws RunTimeException for any other endpoint; chainable
setUserId(), setReceiptId(), setDeveloperSecret(); and validate(), which GETs
developer/{secret}/user/{userId}/receiptId/{receiptId} and returns an Amazon\Response (HTTP
errors are not thrown; they surface as the result code).Amazon\Response class whose result code is the HTTP status: constants RESULT_OK (200),
RESULT_INVALID_RECEIPT (400), RESULT_INVALID_DEVELOPER_SECRET (496), RESULT_INVALID_USER_ID
(497), RESULT_INTERNAL_ERROR (500); methods __construct($httpStatusCode = 200, $jsonResponse = null), getResultCode(), getReceipt(), isValid(), parseJsonResponse().iTunes\Response now keeps the real status for responses that carry no receipt key (for example a bare {"status": 21007}). Since 1.0.4 such responses w
iTunes\Response now keeps the real status for responses that carry no receipt key (for
example a bare {"status": 21007}). Since 1.0.4 such responses were reported as
RESULT_DATA_MALFORMED (21002), which also kept iTunes\Validator::validate() from recognising
21007 and retrying against the sandbox. Thanks to @grEvenX
(#15)iTunes\Response::getLatestReceipt() and iTunes\Response::getLatestReceiptInfo() expose the top-level latest_receipt and latest_receipt_info fields App
iTunes\Response::getLatestReceipt() and iTunes\Response::getLatestReceiptInfo() expose the
top-level latest_receipt and latest_receipt_info fields Apple returns for auto-renewable
subscriptions (populated for iOS 7+ style receipts only). Thanks to @chekalskiy
(#13)iTunes\Validator::setIStoreSharedSecret() now returns $this, so it can be chained like the
other setters. Thanks to @chekalskiy
(#12)Google Play subscription validation: new GooglePlay\Validator::TYPE_PURCHASE and GooglePlay\Validator::TYPE_SUBSCRIPTION constants and a chainable set
GooglePlay\Validator::TYPE_PURCHASE and
GooglePlay\Validator::TYPE_SUBSCRIPTION constants and a chainable
setPurchaseType($purchase_type). validate() calls purchases_subscriptions->get() when the
type is TYPE_SUBSCRIPTION and purchases_products->get() otherwise (the default). Thanks to
@chekalskiy (#11)iTunes\Validator::__construct() defaulted its $endpoint parameter to the bare, undefined constant ENDPOINT_PRODUCTION instead of self::ENDPOINT_PRODUC
iTunes\Validator::__construct() defaulted its $endpoint parameter to the bare, undefined
constant ENDPOINT_PRODUCTION instead of self::ENDPOINT_PRODUCTION, so new Validator() with
no argument raised a notice and then threw RunTimeException("Invalid endpoint 'ENDPOINT_PRODUCTION'"). Thanks to @grEvenX
(#6)GooglePlay\Validator::validate() now calls the Android Publisher API v2
purchases_products->get() instead of the removed v1.1 inapppurchases->get(), so it returns a
Google_Service_AndroidPublisher_ProductPurchase rather than an InappPurchase; composer
google/apiclient was bumped from 1.0.4-beta to ~1.1. Thanks to @whs
(#7)0770 instead of 0777.
(#7)iTunes\Validator::validate($receiptData, $iStoreSharedSecret) stored $receiptData as the shared secret; the second argument is now used, so passing th
iTunes\Validator::validate($receiptData, $iStoreSharedSecret) stored $receiptData as the
shared secret; the second argument is now used, so passing the secret to validate() works for
the first time since it was added in 1.0.6. Thanks to @grEvenX
(#5)guzzle/guzzle constraint relaxed from 3.8.* to ~3.8, allowing Guzzle 3.9. Thanks
to @MaartenStaa (#4)GooglePlay\Validator now caches the OAuth access token in a per-client file, /googleplay_access_token_ .txt, instead of one shared googleplay_access_t
GooglePlay\Validator now caches the OAuth access token in a per-client file,
<sys_get_temp_dir()>/googleplay_access_token_<md5(client_id)>.txt, instead of one shared
googleplay_access_token.txt, so validators for different Google API clients on the same host no
longer overwrite each other's token.Breaking: the misspelled constant iTunes\Response::RESULT_PRODUCTION_RECEIPT_SENT_TO_SENDBOX was renamed to iTunes\Response::RESULT_PRODUCTION_RECEIPT
iTunes\Response::RESULT_PRODUCTION_RECEIPT_SENT_TO_SENDBOX
was renamed to iTunes\Response::RESULT_PRODUCTION_RECEIPT_SENT_TO_SANDBOX (value 21008
unchanged).iTunes\Response only reads receipt.bundle_id (iOS 7+) or receipt.bid (iOS 6) when the key
exists, so receipts without one no longer trigger an undefined-index notice; getBundleId()
returns null in that case.iTunes shared-secret support: iTunes\Validator::setIStoreSharedSecret($secret) and getIStoreSharedSecret(). When set, the secret is sent as the passwo
iTunes\Validator::setIStoreSharedSecret($secret) and
getIStoreSharedSecret(). When set, the secret is sent as the password field of the
verifyReceipt request, as Apple requires for auto-renewable subscriptions. Thanks to @stokic
(#1)iTunes\Response::getBundleId(), returning receipt.bundle_id for iOS 7+ receipts or
receipt.bid for iOS 6 receipts.iTunes\Validator::validate($receiptData = null) gained a second parameter, $iStoreSharedSecret = null. Note that in this release the parameter is mishandled (the receipt data is stored as the
secret); use setIStoreSharedSecret() instead until 1.0.9.
(#1)No user-visible changes. The only edit replaces is_array($receipt['in_app']) > 0 with is_array($receipt['in_app']) in iTunes\Response::parseJsonRespon
is_array($receipt['in_app']) > 0 with
is_array($receipt['in_app']) in iTunes\Response::parseJsonResponse(), which is functionally
equivalent despite the commit message "fixed ios > 7 receipt validation".Support for iOS 7+ unified app receipts in iTunes\Response: when the response contains receipt.in_app, the new iTunes\Response::getPurchases() returns
iTunes\Response: when the response contains
receipt.in_app, the new iTunes\Response::getPurchases() returns that array of in-app
purchases; for iOS 6-style transaction receipts it returns a one-element array wrapping the
receipt itself.iTunes\Response::getReceipt() now returns an empty array instead of null when the response has
no receipt.receipt key is now reported as RESULT_DATA_MALFORMED (21002) regardless
of the status Apple returned. This is a regression for status-only responses such as 21007 and
was fixed in 1.0.13.GooglePlay\Validator no longer prints diagnostics to stdout. The constructor now throws ReceiptValidator\RunTimeException when refreshing the access t
GooglePlay\Validator no longer prints diagnostics to stdout. The constructor now throws
ReceiptValidator\RunTimeException when refreshing the access token fails (previously it echoed
the error and continued), and validate() lets Google API client exceptions propagate instead of
echoing them and returning null./tmp/google_access_token.txt to
<sys_get_temp_dir()>/googleplay_access_token.txt; the file is created (touch) and set to mode
0777 on construction.iTunes\Validator: constants ENDPOINT_PRODUCTION and ENDPOINT_SANDBOX; __construct($endpoint) (throws RunTimeException for any other URL); setReceiptDa
iTunes\Validator: constants ENDPOINT_PRODUCTION and ENDPOINT_SANDBOX;
__construct($endpoint) (throws RunTimeException for any other URL);
setReceiptData()/getReceiptData(), accepting either base64 or raw JSON (JSON is base64-encoded
for you); setEndpoint()/getEndpoint(); and validate($receiptData = null), which POSTs to
Apple's verifyReceipt endpoint with Guzzle 3 and returns an iTunes\Response. When the production
endpoint answers 21007 (sandbox receipt sent to production, as during App Review) the request is
automatically retried against the sandbox. A non-200 HTTP reply throws RunTimeException.iTunes\Response: constants RESULT_OK (0), RESULT_APPSTORE_CANNOT_READ (21000),
RESULT_DATA_MALFORMED (21002), RESULT_RECEIPT_NOT_AUTHENTICATED (21003),
RESULT_SHARED_SECRET_NOT_MATCH (21004), RESULT_RECEIPT_SERVER_UNAVAILABLE (21005),
RESULT_RECEIPT_VALID_BUT_SUB_EXPIRED (21006), RESULT_SANDBOX_RECEIPT_SENT_TO_PRODUCTION
(21007), RESULT_PRODUCTION_RECEIPT_SENT_TO_SENDBOX (21008, sic); methods getResultCode(),
setResultCode(), getReceipt(), isValid(), parseJsonResponse().GooglePlay\Validator: __construct(array $options) taking client_id, client_secret and
refresh_token, caching the OAuth access token in /tmp/google_access_token.txt; chainable
setPackageName(), setPurchaseToken(), setProductId(); and validate(), returning the
Android Publisher inapppurchases->get() result, or null (with the error echoed to stdout) on
failure.ReceiptValidator\RunTimeException, extending \Exception.aporat/store-receipt-validator requiring PHP >=5.4, guzzle/guzzle 3.8.*
and google/apiclient 1.0.4-beta, with PSR-0 autoloading of the ReceiptValidator namespace from
src/.Your coding agent can read these notes before it upgrades. Set up the MCP server →