As a developer, encountering the error message "The item you requested is not available for purchase" can disrupt the user experience and affect your app’s revenue. In this article, we will explore the various causes of this error and provide a detailed guide on how to troubleshoot and resolve it
One of the most common errors you might encounter implementing Google Play Billing is BillingResponseCode.ITEM_UNAVAILABLE (code 4). The corresponding message from the BillingClient API is frustratingly vague. Here's how to fix it.
Ensure your app has been uploaded to the Google Play Console at least once and assigned to a test track (internal, alpha, or beta).
Confirm that the in-app purchase or subscription product is active and properly configured in the Play Console. Inactive products cannot be bought.
Add approved testers to your internal, alpha, or beta test tracks. These testers must also join the test via the provided link in the Play Console.
Under Setup > License Testing, add the Google accounts of your testers. This allows them to make test purchases using test credit cards, avoiding actual charges.
Ensure the Google Play account signed in on the testing device matches the one added for testing and license testing. This account must be the active account before attempting any test purchases.
Implementing Google Play Billing can be complex and time-consuming. Nami offers a simplified solution with a proven implementation that covers all tricky edge cases. Our SDK is easy to adopt, requires no server-side code, and includes built-in native paywall templates, A/B testing, and analytics. With Nami, you can focus on creating a great app experience without the hassle of Play Billing infrastructure.
Now you can focus on building a great app experience, not hassling with Play Billing infrastructure. Get started for free here.
Addressing the "The item you requested is not available for purchase" error requires a thorough understanding of your app’s architecture and integration with external services. By following the steps outlined above, you can effectively troubleshoot and resolve the issue, ensuring a seamless user experience and maximizing your app’s revenue potential.
For more insights and solutions to common app development challenges, visit our website at namiml.com. Explore our low-code solutions to streamline your development process and enhance your app’s functionality.
Understanding Apple's fiscal calendar is essential for app developers to manage revenue effectively. Apple's fiscal year starts on the last Sunday of September, differing from the typical calendar year. This guide provides a detailed overview of the Apple Fiscal Calendar for 2024, including key dates and payment schedules, ensuring you stay informed and optimize your financial planning.
Apple's fiscal year is a structured schedule when Apple distributes app revenue to developers after deducting App Store fees and sales taxes. This schedule changes slightly every year, so it's crucial for developers and product managers to stay updated. Apple’s fiscal year is distinct from the typical fiscal year used by most companies, making it essential to understand its unique structure.
Apple’s fiscal year starts on the last Sunday of September and consists of four quarters, each lasting three months. The key points are:
Fiscal Month | Payment Date | Revenue Period |
---|---|---|
October 2023 | October 5, 2023 | August 6 – September 2, 2023 |
November 2023 | November 2, 2023 | September 3 – September 30, 2023 |
December 2023 | December 7, 2023 | October 1 – November 4, 2023 |
January 2024 | January 4, 2024 | November 5 – December 2, 2023 |
February 2024 | February 1, 2024 | December 3 – December 30, 2023 |
March 2024 | March 7, 2024 | December 31, 2023 – February 3, 2024 |
April 2024 | April 4, 2024 | February 4 – March 2, 2024 |
May 2024 | May 2, 2024 | March 3 – March 30, 2024 |
June 2024 | June 6, 2024 | March 31 – May 4, 2024 |
July 2024 | July 4, 2024 | May 5 – June 1, 2024 |
August 2024 | August 1, 2024 | June 2 – June 29, 2024 |
September 2024 | September 5, 2024 | June 30 – August 3, 2024 |
Here is an online Apple payout calendar resource you can bookmark that is updated for the current fiscal year.
You can’t access the revenue in your Developer Account until the payment date. This 33-day gap can disrupt your app’s financial flow, especially if you rely on paid traffic for app promotion. Managing your finances to account for this gap is crucial to avoid running out of funds.
Ad networks require timely payments, and any pauses in your promotional activities due to insufficient funds can negatively impact your user acquisition strategy. Restarting ads after a pause can reduce their effectiveness. Proper financial planning helps maintain continuous ad campaigns.
Align your performance metrics with your actual earnings. For instance, revenue earned in September will actually be from August 6 to September 2, but your spending might be based on the calendar month. Synchronize your financial records with Apple’s fiscal periods for accurate analysis.
Apple pays developers proceeds for app or in-app purchase sales within 45 days of the last day of the fiscal month in which the transaction was completed.
Payments are made only if the following is true:
Apple consolidates proceeds so you can expect a single payment to your bank each fiscal period.
Navigating Apple's unique fiscal year can be intricate, but understanding its structure is vital for managing your app’s revenue. By staying informed about key dates and payment schedules, you can optimize your financial planning and ensure a smooth revenue cycle.
Enhance your financial proficiency and in-app monetization by leveraging the Apple Fiscal Calendar 2024. Stay ahead in managing your app’s revenue effectively.
The iOS App Shared Secret is a crucial component in the development and management of applications within Apple's ecosystem. This secret key is used primarily to enhance security and streamline the validation of in-app purchases, ensuring that only authorized transactions are processed. In this article, we will delve into the concept of the iOS App Shared Secret, explore the different types available, and provide a comprehensive guide on how to generate one.
The iOS App Shared Secret is a unique, 32-character hexadecimal string private key that developers use to secure in-app purchase transactions. This key ensures that communications between your app and Apple's servers are authenticated, preventing unauthorized access and fraudulent transactions.
It is especially vital for apps with subscription-based models, where the key is used to validate and renew subscriptions securely. It works fantastic for server-side receipt validation and provides added security for receipts with auto-renewable subscriptions.
Receipt verification is a process app developers use to verify purchases. Specifically, this process if for purchases made using Apple’s App Store payments mechanism (aka StoreKit). The receipt provides a complete list of all the purchases made by an app’s user. The receipt includes both in-app purchases and subscriptions.
Apple recommends that app developers validate a receipt for security and piracy reasons. In fact, property security requires a secure backend.
The Shared Secret allows you to receive the decoded form of a receipt. In addition, its included in the payload of App Store Server Notifications. You can check that the password key’s value matches the known Shared Secret verify the authenticity of the notification.
There are primarily two types of shared secrets you might encounter in the iOS development environment:
Generating an iOS App Shared Secret is a straightforward process. Here’s a step-by-step guide:
Visit App Store Connect and log in with your Apple Developer account credentials. To generate either type of Shared Secret requires an App Store Connect account with either Account Holder or Admin role.
Navigate to the "My Apps" section and select the app for which you need to create a shared secret.
In the app's dashboard, go to the "Features" tab and select "In-App Purchases".
If you are generating an app-specific shared secret, find the section labeled "App-Specific Shared Secret" and click on "Generate" or "Reset" if a key already exists.
For a primary shared secret, go to "Users and Access" and select "Shared Secret" from the sidebar. Click on "Generate" to create a new key.
The App-Specific Shared Secret is a good idea if you want app-level security. Perhaps plan to transfer an app to another Apple Developer. For instance, if you sell an app to another party on a marketplace like Flippa.
Once generated, copy the shared secret and store it securely. It will be needed for integrating your app's in-app purchase functionality with your backend server.
Proper management of your shared secret is crucial for maintaining app security. Here are some best practices:
Understanding and effectively managing your iOS App Shared Secret is essential for maintaining the security and integrity of your app's in-app purchases. By following the steps outlined in this article, you can ensure that your app's transactions are secure, providing a better experience for your users and peace of mind for yourself.
For more detailed guidance on app development and in-app purchase security, visit our website at NamiML to explore our low-code solutions designed to simplify and enhance your app's functionality.
Discover how to personalize your app paywalls to user context, device, and journey stage. Unlock tactics of paywall A/B testing to craft frictionless experiences that drive conversions.
Paywalls offer a clear path to monetization for subscription businesses. However, neglecting user experience in the pursuit of conversions is short-sighted. Frustrated users won't convert, and worse, won't return. A well-designed paywall that showcases value and prioritizes user experience can drive sustainable growth.
Great design is a cornerstone of user experience, but it's only part of the picture. Effective UX is all about understanding how users' needs and expectations shift depending on the context.
Imagine a football fan on their morning commute – they're quickly checking scores on their phone. But later, relaxing at home, they're engrossed in the game on their smart TV. A one-size-fits-all paywall won't resonate with these different user journeys.
Similarly, a user’s journey within the app also influences their receptiveness to a paywall. A new user might be curious about the app's core features, while a returning user might be more interested in premium functionalities that fit into the workflow they have already established for themselves within the app.
This is where context-aware paywall optimization comes in. By understanding user intent, device usage, and the stage of their journey within the app, you can tailor paywall messaging and design to resonate with individual users.
How do you stand to gain from this?
Not only does a user-centric paywall lead to higher conversion rates but also makes a positive user experience possible. It keeps users engaged in the long run, so you can hope for them to bring in recurring business over an extended period.
In this article, we delve into the power of context-aware paywall optimization. We'll explore the limitations of generic paywalls, discuss the importance of understanding user context and journey, and dive into strategies for creating personalized experiences that drive conversions – all with the intention of serving users the right kind of paywall that doesn't block access, but rather takes them to the other side where the grass is greener, so to speak.
Think of a supermarket where every product has a generic sign that screams "Buy this now!". That wouldn’t be very interesting or effective, would it?
Wouldn’t it make more sense to see targeted messages like "healthy yogurt for your morning routine" or "fresh vegetables for your family dinner"? Paywalls that cater to user context are like those targeted signs – more relevant and likely to resonate.
People have different needs and expectations. A busy professional on their mobile phone while traveling to their office seeks a quick and informative experience, while someone relaxing on the couch with a smart TV? Well, they don’t mind browsing a wider range of content with previews or trailers on offer. A one-size-fits-all paywall ignores these crucial differences, leading to user frustration and lost opportunities for conversions.
The key to unlocking effective paywalls lies in understanding the context in which users encounter the paywall. This is where user data, gathered with the user’s consent, of course, becomes your best friend.
Here are some key factors to consider –
A user's journey within the app is another critical piece of the puzzle. People don't just appear in your app fully ready to convert to paying subscribers. They go through distinct stages, each with unique needs and expectations.
Here's how you can tailor your paywall experience to each stage in the user journey –
And let’s say you know from tracking analytics that the user prefers cardio-related workouts to strength-training ones, then, offering a paywall that highlights "cardio-related" activities can help seal the deal. Such an approach demonstrates a deep understanding of a user’s current usage patterns and offers a clear path to, not only monetization for your business but also for enhancing the user’s experience.
In addition to the user journey stage, the kind of device the user is using also affects how they interact with paywalls.
Let’s take a look at the popular devices –
Just like with any marketing strategy, the key to successful paywalls lies in continuous testing and refinement. A/B testing allows you to tailor your paywall experience to different user segments, devices, and journey stages.
Here's how it works –
Imagine you have two different paywall versions - one focusing on exclusive video content and another highlighting in-depth articles. You can use A/B testing to present these variations to users on different devices.
By analyzing conversion rates, you can see which version resonates better on each of your devices. Chances are, the video paywall does best on smart TV while the one with in-depth articles is better suited for a desktop app. For a deeper dive into A/B testing paywalls on mobile apps, check out our article on A/B testing mobile app paywalls for maximizing revenue and user engagement.
A/B testing can also be used to personalize the paywall experience based on a user's journey stage. A new user who just downloaded the app might see a paywall with a free trial offer, while a user who has been actively engaged for a month might encounter a paywall highlighting premium features. This targeted approach feels less intrusive and increases the chance of a conversion.
Here are some A/B testing best practices to keep in mind –
Monetization is crucial for subscription businesses, and paywalls are a key tool to achieve that goal. However, a paywall that disrupts the user experience is more likely to drive users away than towards subscriptions. The key lies in a user-centric approach – i.e. delivering personalized paywalls that resonate with individual needs and context.
Unlocking this strategy starts with understanding your users. Analyze user data to see where they come from, what devices they use, and how they engage with your app. Knowing their journey stage – whether they are new users or long-time engaged users – is also essential. This data empowers you to craft the right message, design, and timing for your paywalls.
A/B testing is your next step. Test different paywall variations to see which ones convert best on specific devices and in different user contexts. By iteratively refining your approach, you can create frictionless paywalls that seamlessly guide users toward subscriptions.
Ready to build and iterate on your paywalls? Nami ML can help! Schedule a free consultation with our experts or sign up for a free trial of our paywall builder. Start creating and testing personalized paywalls in minutes.
With the power of a thousand paywalls, a company can now run countless A/B tests and granularly target users to give every customer a personalized purchasing experience. The result is accelerating revenue from a wave of loyal customers.
DENVER, 5 June 2024 –Nami® ML today launched a groundbreaking new version of their Subscription Studio platform, empowering businesses to take their revenue from a trickle to a tidal wave.
While subscription services swelled over the past decade, the individualized experience that today’s consumers demand has been arduous to come by, primarily because producing any purchasing experience takes a lot of time and resources - it can take hundreds of hours to design, develop, deploy, test, and refine a single paywall, and those hours skyrocket when a subscription is offered at-scale across a variety of platforms and ecosystems.
With Nami’s updated Paywall Creator, companies can adjust pricing, alter positioning or launch a new design in minutes– everywhere they sell. If a company can create one paywall in ten minutes, then they can create a thousand paywalls in about the amount of time it previously took to create a single paywall.
With the power of a thousand paywalls, a company can run countless A/B tests and granularly target users to give every customer a personalized purchasing experience.
“The best way to grow subscription revenue is to get personal. Nami customers rapidly discover what resonates, which translates to more revenue. Nami pays for itself after a few tests, ” explains Dan Burcaw, Co-Founder, CEO of Nami ML.
Nami’s updated platform makes it easy to discover what each customer wants, then deliver a level of personalization never before achieved at scale. Paywalls can be contextualized at different moments - as your trial expires or when trying to access premium content, for example - or target CRM audience segments or even individual users.
Today’s release transforms how companies approach subscriptions. No longer is a paywall a mere product selection point - it is a marketing asset.
Burcaw continues, “The purchase experience is now in harmony with other marketing channels for a truly orchestrated subscriber journey. The result is accelerating revenue from a wave of loyal customers.”
Nami ML helps brands deploy personalized purchasing experiences that foster long-lasting loyalty and drive revenue. Visit https://namiml.com and request a demo from a product expert today.
Nami is a registered trademark of Nami ML Inc.
Verifying receipts for in-app purchases is crucial for maintaining the integrity of transactions within your iOS app. This process ensures that the purchases made by users are legitimate, protecting your app from potential fraud. In this article, we'll cover the essential steps for verifying App Store receipts. We'll explore the process of receipt validation, best practices for secure implementation, and troubleshooting common issues.
Update: Since this guide was published, Apple announced StoreKit 2 which adds new capabilities and APIs such as Signed Transactions. StoreKit 1-style receipts and APIs will continue to be available, so we hope this article will continue to be a useful reference. If you're interested in learning more about StoreKit 2, head over here. Also, the new APIs are a major change and another and a great time to build atop Nami which abstracts away these underlying implementation details.
An App Store receipt is a digital proof of purchase generated by Apple for any transaction made within the App Store, including app purchases, in-app purchases, and subscriptions. This receipt is a critical component in ensuring the integrity and authenticity of transactions, as it contains detailed information about the purchase, such as the product ID, transaction ID, purchase date, and subscription details if applicable.
The receipt is stored on the user's device and can be retrieved and verified to confirm that the user has made a legitimate purchase. This verification process typically involves sending the receipt data to Apple's servers, where it is checked for validity and authenticity. By verifying receipts, app developers can protect their apps from fraudulent transactions and unauthorized access to premium content or features.
The receipt data provided by StoreKit is a Base64-encoded. To receive a decoded version, described in detail in this article, you need to transmit the encoded receipt to Apple’s verifyReceipt endpoint. To fully understand verifyReceipt, see this article where we code a simple Python script step-by-step that demonstrates how to send an encoded receipt and receive the decoded receipt.
Why is all of this necessary? The two most important reasons:
The mechanics of receipt validation are relatively straightforward as demonstrated in the Python script referenced early.
However, grokking the decoded receipt itself is painful. It’s not the most elegant data structure in the world, and there are all sorts of esoteric details and nuances. Our goal with this article is build upon and go beyond Apple’s documentation to help you better understand the decoded receipt and avoid common (and more exotic) pitfalls.
The first step is to obtain the receipt data from the app. This data can be retrieved using the SKReceiptRefreshRequest
class or from the receipt URL located in the app’s bundle.
if let appStoreReceiptURL = Bundle.main.appStoreReceiptURL,
FileManager.default.fileExists(atPath: appStoreReceiptURL.path) {
do {
let receiptData = try Data(contentsOf: appStoreReceiptURL, options: .alwaysMapped)
let receiptString = receiptData.base64EncodedString(options: [])
// Send receiptString to your server for verification
} catch {
print("Couldn't read receipt data with error: " + error.localizedDescription)
}
}
Once you have the receipt data, you need to send it to your server. This step is crucial because the verification process should be performed on a secure server to prevent tampering. Your server will then forward the receipt data to Apple's verification server.
Your server should send a request to Apple’s verification server at the following URL:
https://buy.itunes.apple.com/verifyReceipt
https://sandbox.itunes.apple.com/verifyReceipt
The request should include the receipt data and your app’s shared secret for subscriptions. Here's an example using Swift's URLSession
to send the receipt data:
Apple’s response will include the status of the receipt and, if valid, the receipt’s details. Your server should parse this response and take appropriate action, such as granting the purchased content or renewing a subscription.
let requestDictionary: [String: Any] = ["receipt-data": receiptString, "password": "your_shared_secret"]
guard JSONSerialization.isValidJSONObject(requestDictionary) else { return }
do {
let requestData = try JSONSerialization.data(withJSONObject: requestDictionary, options: [])
let storeURL = URL(string: "https://buy.itunes.apple.com/verifyReceipt")!
var request = URLRequest(url: storeURL)
request.httpMethod = "POST"
request.httpBody = requestData
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
let task = URLSession.shared.dataTask(with: request) { data, response, error in
guard error == nil, let data = data else {
print("Error verifying receipt: \(error?.localizedDescription ?? "No data")")
return
}
// Process the verification response
}
task.resume()
} catch {
print("JSON serialization failed: \(error.localizedDescription)")
}
The responseBody JSON payload can be daunting (example), especially if the receipt belongs to an end-user with a lengthy past purchase history.
It turns out, that there are somewhere between 3 and 7 top-level keys in the verifyReceipt responseBody JSON data structure. Here are the keys:
Some of the top-level keys are present only in certain circumstances. Here’s a summary of each top-level key, what it is, and when you should expect to see it in the responseBody:
Let’s dig into each of these top-level elements in detail, starting with response metadata keys: status, is-retryable, environment.
Upon received a response from verifyReceipt, the first thing you should do is check that status key. If the value is 0, the receipt is valid. Otherwise, an error occurred and you will need to interpret the status code to know what to do next.
ProTip: Notice that the resolution to a number of the status codes is to retry later. It’s important that your receipt validation service is robust enough to handle a wide variety of scenarios including: network timeouts, HTTP status codes such as 503, and also status codes embedded in the return payload of a HTTP 200 response. In some of these cases, you may want to to support retry logic to try again immediately. If you still failed to validate the receipt, you may want to add them to job queue for a future processing attempt.
If the Apple status value is within the range of 21100-21199, the is-retryable key should be present in the response payload. Apple’s documentation says the type is boolean, but the provided values are 0 and 1, not the proper JSON boolean values of true and false.
The environment value tells you which Apple environment the receipt was generated from.
Now let’s look beyond the metadata into the primary keys containing the responseBody’s substance: receipt, latest_receipt_info, pending_renewal_info.
The value of receipt, a key within the decoded receipt response, is a JSON object containing the decoded receipt data.
This key always exists, whether a user has made an in-app purchase or not.
In fact, if you follow the best practice and send receipt data from your app to a server you will end up storing and decoding (via receipt validation) many receipts, containing this receipt key with certain metadata even if there have be no in-app purchases.
Consider the history of the App Store. It was built atop the foundations of the iTunes Music Store. On iTunes, an email receipt was sent to the user whether the song or album cost money or was free. This lineage carried on to the App Store, where in the early days there were just two types of apps: paid and free.
Once in-app purchases were added, additional context was introduced to this data structure. In fact, it contains the full context you need to understand the purchase.
Now, with the advent of subscriptions things have gotten a bit more complicated. Some context about the subscription in-app purchase exists in this data structure, but some does not. For auto-renewable subscriptions, you will find additional context inside the latest_receipt_info and pending_renewal_info top level keys which we go into more detail later in this article.
Jump to an element-by-element breakdown of the receipt data structure.
The latest Base64 encoded app receipt. This field is only returned if the app receipts contains an auto-renewable subscriptions.
An array of JSON objects representing in-app purchase transactions for auto-renewable subscriptions. This field is only returned if the app receipts contains an auto-renewable subscriptions.
ProTip: If your verifyReceipt request includes the field exclude-old-transactions set to true, only the latest auto-renewable subscription transaction will be included in this field in the response.
Jump to an element-by-element breakdown of the latest_receipt_info data structure.
An array of JSON objects representing open (pending) or failed auto-renewable subscription renewals as identified by the product_id. This field is only returned for app receipts that contain auto-renewable subscriptions.
Jump to an element-by-element breakdown of the pending_renewal_info data structure.
Let’s take a look at all of the possible elements you may encounter in the receipt JSON object.
A unique 64-bit long integer generated by App Store Connect. This is used by the App Store to unique identify the app associated with the receipt.
In production, expect a unique identifier. In sandbox, this field will be populated with a 0.
The current version of the app installed on the user’s device at the time of the receipt’s receipt_creation_date_ms. The version number is based upon the CFBundleVersion (iOS) or CFBundleShortVersionString (macOS) from the Info.plist.
In the sandbox, the value is always “1.0”.
The bundle identifier for the app to which the receipt belongs. You provide this string on App Store Connect and it corresponds to the value of CFBundleIdentifier in the Info.plist file of the app.
A unique identifier for the app download transaction.
While not well documented by Apple, this unique value appears to be tied to the download transaction represented by original_application_version and original_purchase_date.
In production, expect a unique identifier. In sandbox, this field has been observed to be populated with a 0.
The time the receipt expires for apps purchased through the Volume Purchase Program (VPP).
See expiration_date
See expiration_date
An array containing in-app purchase receipt fields for all in-app purchase transactions.
The original version of the app installed on the user’s device. For example, if the current app version is 2.0 but the user originally had version 1.8 installed, this value would be “1.8”. For the current version installed, see application_version.
The version number is based upon the CFBundleVersion (iOS) or CFBundleShortVersionString (macOS) from the Info.plist.
In the sandbox, the value is always “1.0”.
The time of the original app purchase.
The time the user ordered the app available for pre-order. This field is only present if the user pre-orders the app.
See preorder_date
See preorder_date
The time the App Store generated the receipt.
The type of receipt generated. The value corresponds to the environment in which the App Store or VPP purchase was made.
The time the request to the verifyReceipt endpoint was processed and the response was generated.
See request_date
See request_date
An arbitrary number that identifies a revision of your app. In the sandbox, this key's value is “0”.
An array of JSON object(s) found at latest_receipt_info contains in-app purchase transactions for auto-renewable subscriptions. This field is only returned if the app receipts contains an auto-renewable subscriptions.
ProTip: If your verifyReceipt request includes the field exclude-old-transactions set to true, only the latest auto-renewable subscription transaction will be included in this field in the response.
The time of the subscription was canceled. This can happen for one of the following reasons:
The reason for a refunded transaction. When a customer cancels a transaction, the App Store gives them a refund and provides a value for this key.
The time a subscription expires or when it will renew.
See expires_date
See expires_date
Indicates whether the user is the purchaser of the product, or is a family member with access to the product through Family Sharing.
Indicates whether or not an auto-renewable subscription is in the introductory price period.
An indicator of whether a subscription is in the free trial period.
Indicates a subscription has been canceled due to an upgrade. Only present for upgrade transactions.
Check cancellation_date to determine when the cancellation occurred.
The reference name of a subscription offer that you configured in App Store Connect. Present when a customer redeemed a subscription offer code.
The time of the original auto-renewable subscription purchase.
The transaction identifier of the original purchase.
The unique identifier for the product purchased as configured for that product in App Store Connect.
The identifier of the subscription offer redeemed by the user.
The time the App Store charged the user’s account for a subscription purchase or renewal.
See purchase_date
See purchase_date
The number of consumable products purchased. Included but meaningless in the latest_receipt_info data structure, which is for auto-renewable subscriptions only. Likely an artifact from Apple internally sharing data structures between responseBody.receipt.in_app and responseBody.latest_receipt_info.
The identifier of the subscription group to which the subscription belongs.
A unique identifier for purchase events across devices, including subscription-renewal events. This value is the primary key for identifying subscription purchases.
A unique identifier for a transaction such as a purchase, restore, or renewal.
If transaction_id and original_transaction_id match, the transaction is a purchase. Otherwise, the transaction is a restore or renewal.
The array of JSON object(s) found at pending_renewal_info for open (pending) or failed auto-renewable subscription renewals includes a number of possible fields. Let’s take a look.
The product identifier for the customer’s pending subscription renewal, if the user has downgraded or crossgraded to a subscription of a different duration for the subsequent subscription period.
The renewal status for the auto-renewable subscription.
If this value is “0”, the customer is showing a likelihood to churn out of the subscription at the end of the current renewal period. A best practice is to present the user with an attractive upgrade or downgrade offer in the same subscription group.
The reason a subscription expired.
If this value is “1”, you may want to offer the user an alternative subscription or a “winback” offer.
If the value is “2”, you may want to prompt them to subscribe to the same product again since they churned involuntarily.
The time at which the grace period for subscription renewals expires.
Indicates whether the user’s auto-renewable subscription is in the billing retry period.
If the value is “1”, consider prompting the user to update their billing information.
The reference name of a subscription offer that you configured in App Store Connect. Present when a customer redeemed a subscription offer code.
The transaction identifier of the original purchase.
The price consent status for a subscription price increase.
The unique identifier for the product purchased as configured for that product in App Store Connect.
Throughout the Apple decoded receipt payload, you will find three different date formats expressed whenever a date is provided:
What is ISO 8601-like? Apple documentation refers to “a date-time format similar to the ISO 8601” and “an RFC 3339 date”. In reality it’s not strictly either. The timezone part of the string make it difficult to convert these string-based date formats into date time object using common libraries which except ISO 8601 or other common standards.
Why Apple uses this specific non-standard format is probably an esoteric story related to the origins of NeXT or WebObjects.
To dive deeper into these three formats, let’s take a look at the date found at responseBody.receipt.receipt_creation_date. This is date the receipt was created expressed in the ISO 8601-like format relative to Greenwich Meantime (GMT).
Each base date key has two modifiers: _pst and _ms with the ISO 8601-like PST and Milliseconds since Unix epoch representations.
responseBody.receipt.receipt_creation_date_pst is an ISO 8601-like date relative to Pacific Standard Time (PST). Please note, even though the timezone modifier is provided in the format America/Los_Angeles instead of a UTC offset, the date provided is indeed Pacific Standard Time. Your code needs to be careful during Pacific Daylight Time (PDT), since this value will still be a Pacific Standard Time date.
responseBody.receipt.receipt_creation_date_ms is when the receipt was created in the number of milliseconds since the Unix epoch time. The _ms modifier for all date fields in an Apple decoded receipt is the far easiest to work with. Most modern languages can convert this to a native date time object out of the box or with commonly used libraries.
VPP stands for Volume Purchase Program. It's essentially how Apple provides an App Store-like experience for educational institutions or businesses to acquire and deploy apps at scale. As it relates to this article, some receipt fields only show up or contain values for transactions that occur on the VPP store.
As this guide illustrates, understanding the decoded receipt is complicated. So is the code necessary to store, validate, and make meaning out of the decoded receipt so you can properly manage your paid customer base.
The esoteric and sometimes arcane receipt response is the result of years of change and new features bolted on to the App Store year after year. It’s incredibly complex to write rock solid code to manage receipts and leverage all the App Store ecosystem has to offer related to in-app purchase and subscription monetization.
That’s where Nami can help. We’ve built a complete service to help you sell in-app purchases or subscriptions. In addition to a variety of features, we handle all client and server side details involving the receipt. In fact, you also don't have to write a single line of StoreKit 1 or StoreKit 2 code.
Our code has encountered many of the hard to test edge cases you’ll see encounter over time if you do-it-yourself. Our service has extensive test coverage and operates at scale.
This means you can stay focused on building your app experience. If you’re interested in learning more, we’d love to chat.