This documentation describes what kind of data we should be sending to Apple server, once we are receiving CONSUMPTION_REQUEST
https://developer.apple.com/documentation/appstoreserverapi/consumptionrequest
But, it doesn't describe what kind of data we are receiving, when we are receiving CONSUMPTION_REQUEST?
May I know, is such a document available?
Thank you.
StoreKit
RSS for tagSupport in-app purchases and interactions with the App Store using StoreKit.
Post
Replies
Boosts
Views
Activity
I’m updating an old app which uses StoreKit and hosted in-app purchases (multimedia add-ons).
The original .pkgs are missing and the only known place they exist intact is on Apple’s servers.
The app is able to download and install the IAPs. Is there anyway to save them to the local device?
I am the Lead iOS Developer for The Incc, an upcoming social networking application. The platform offers subscriptions that grant users access to premium content, primarily digital magazines showcasing diverse cultures, alongside standard social media features and additional unique functionalities.
I am exploring two specific use cases for our subscription model.
Promo Codes with Split Payments:
We plan to collaborate with the our influencers (referred to as Mover Shakers) by providing them with promo codes for users to purchase subscriptions. For such purchases, we aim to implement a revenue split model, allocating 10% to the influencer and the remainder to us after Apple’s fees.
Gifting Subscriptions:
We also wish to enable users to gift subscriptions to others within the app.
I understand that the Apple Subscription Service does not natively support these features.
What other options do we have to achieve this that are also not against the Apple's guidelines.
Hello, Apple App Store Server API Team!
I have one questions about the identifiers provided by Apple App Store Server API. Could you please answer?
We are running an iOS App. In our app, the transactionId we get from Apple App Store Server API is called T1. (Example)
Q1. Is it correct that other iOS apps cannot get T1 for transactionId from Apple App Store Server API? (I'm wondering if the transactionId is globally unique across apps.)
Thank you!
I'm using Transaction.environment to determine server behavior.
https://developer.apple.com/documentation/storekit/apptransaction/environment
https://developer.apple.com/documentation/storekit/appstore/environment
I gather this information on the app using StoreKit and then send it to the server:
originalTransactionId = transaction.originalID
originalTransactionEnvironment = transaction.environment
When testing within Xcode, on a simulator, the value sent to the server for originalTransactionEnvironment is Xcode - as expected.
When testing on a device using a TestFlight build, the value sent to the server for originalTransactionEnvironment is undefined/nil. I expected it to be Sandbox - and later in production it should be Production.
Most importantly, the value sent to the server for originalTransactionId in the TestFlight version is not undefined/nil - it is the value I expected it would be. The transaction was originally for a subscription purchase, if that makes a difference.
So the transaction is available, and information like originalID is also available. Why is transaction.environment not available? What is the behavior in production?
We have a user that's shared screenshots indicating that they're the recipient of a subscription to our app through Family Sharing, but when we look for the transaction in Transaction.currentEntitlements there's nothing there, even after calling AppStore.sync. Should we expect to see a transaction in current entitlements? How can we determine whether it's our bug or Apple's?
I have several ObjC based apps in the App Store and used to validate the receipt file inside the app in my code, and then reject it with exit(173) if it's invalid, which did trigger macOS to update the receipt if possible.
This isn't working any more in recent macOS versions, where the user is instead just told that the app is damaged, and they need to re-install it manually. Which sucks.
So I wanted to update my code. I read about SKReceiptRefreshRequest, which is supposed to re-download and install the receipt file, if I understand it correctly.
I implemented the code but now have trouble verifying that it works as intended, and does this in a user friendly way.
I found in my tests that macOS now caches the receipt in ~/Library/Caches/com.apple.appstoreagent/fsCachedData and then hardlinks the file into the app.
BTW: Sadly, this also requires that the app is located on the startup volume or the system will refuse to install the receipt, which wasn't a requirement in past times.
Now, if the receipt is already present in the cache folder, then my code works - the receipt gets re-linked.
But what if the cached receipt isn't there, yet? Such as that the user had copied the app from another Mac over to a freshly installed Mac? In the past, when the user then launched the app on the new Mac, he'd be prompted to login to the MAS and if that worked, the receipt would get installed and the app launched.
Basically, the question is: What if the receipt validation fails in my app and I request a new receipt, but the user has not yet logged into MAS (e.g. new computer)?
To simulate this, I logging out of the MAS and TestFlight, deleting all copies of the app and then run the app that I had copied from another Mac where it was authorized with a valid receipt for that device.
If I do this with the old version that uses exit(173), I get these two messages in macOS 15.2:
The second one is especially terrible because it shows the translocated path, which the average user surely get quite confused, and then maybe even search in vain for the app in there and get frustrated. But that's out of my hands. Sigh.
Now, that was proving that the old method with exit(173) isn't working any more and needs to be changed in my apps.
Since I'm still developing (testing) this new behavior, the app is therefore not in the MAS yet - the only way for me to test this is to use TestFlight. However, running a Testflight app copied from another Mac leads to this error:
That is not helpful in simulating what would happen if this app was released in the MAS. This won't let me find out what happens if my app is run on a Mac where the receipt fails and I ask it to load it via SKReceiptRefreshRequest and if the user is NOT yet logged into the MAS account for this purchased app of his/hers.
That leaves only one option: Release the app with untested code and hope for the best.
Contrary to this new behavior, the old method did let me test this easily because I would just use the special App Store tester account with the MAS app, i.e. the built MAS app would, when I launched it locally, request for a login and I'd provide my tester's account. But this isn't available any more, apparently.
What a mess.
So, we've implemented IAP with StoreKit2 e2e for both the client and backend and it's working mostly perfectly, however we have an issue which we can't seem to understand Apple's behavior.
So, imagine a purchase that occurred on the 10th of December in the sandbox environment through an install from TestFlight, and this same purchase keeps getting queued in the Transaction.unfinished list for some reason (today, the 17th of December, a whole week after the initial purchase!).
Here's the flow:
We iterate the list on app launch
Send the unfinishedTransaction's transactionId to our servers
The server says "hey, we could verify it with Apple but it's not longer active (expired). We can't really work with it so just finish it on your side and don't send it to me again"
The client finishes!! the transaction
On the very next app launch it keeps reappearing in the Transaction.unfinished queue
Are we doing something wrong, why doesn't it get cleaned? Is this an expected behavior?
Note, this is generally the server's logic for new purchases:
If we weren't able to contact Apple - we tell the client to not finish the transaction so that we'll be able to re-iterate it on the next app launch or retry
If the transaction is not expired and valid - we update our records and tell the client to finish the transaction
If we couldn't update our records - we tell the client to not finish the transaction so that we'll be able to re-iterate it on the next app launch or retry
If the transaction is not valid for some reason - we tell the user to finish the transaction
The server might see it as a valid transaction if we'll send the originalTransactionId instead of the 'transactionId(using it to callgetTransactionInfoinAppStoreServerLibrary`), but is this something we want to do?
This will obviously not fix the problem because the server tells the client to finish the transaction anyways, but it simply doesn't work.
Please, any advice or changes to make to either Client/Server would be greatly appreciated
Best Regards, Ofek
A user made an in-app purchase on December 15th at around 4:00 PM Japan Standard Time (JST). However, the confirmation email sent after the purchase shows the receipt date as December 14th.
What could be causing this discrepancy in the date?
The app store's primary language is set to English.
The server region is singapore.
Payment completed with error: Error Domain=ASDErrorDomain Code=500 "(null)" UserInfo={client-environment-type=Sandbox, storefront-country-code=USA, NSUnderlyingError=0x2828a5950 {Error Domain=AMSErrorDomain Code=305 "Purchase Failed" UserInfo={NSLocalizedDescription=Purchase Failed, AMSURL=https://sandbox.itunes.apple.com/WebObjects/MZBuy.woa/wa/inAppBuy?guid=00008030-000164513691402E, AMSStatusCode=200, AMSServerPayload={
"cancel-purchase-batch" = 1;
customerMessage = "Bad Gateway_message";
failureType = "";
"m-allowed" = 0;
pings = (
);
}, NSLocalizedFailureReason=Server canceled the purchase}}}
The documentation mentions the following:
Verify your receipt first with the production URL; then verify with the sandbox URL if you receive a 21007 status code. This approach ensures you don’t have to switch between URLs while your app is in testing, in review by App Review, or live in the App Store.
This way, you can use one server environment to handle both Sandbox and Production environments. It is necessary to pass App Review.
However, I'm not manually hitting these URLs - I'm using Apple's libraries.
Specifically, the environment is used in SignedDataVerifier and AppStoreServerAPIClient.
(I can't link to these because, for some reason, the domain apple.github.io is not allowed. The documentation for these is only found there. You can find it quickly by searching these terms and the domain.)
Here is how SignedDataVerifier is being used:
const verifier = new SignedDataVerifier(
appleRootCertificates,
APPLE_ENABLE_ONLINE_CHECKS,
APPLE_ENVIRONMENT,
APPLE_BUNDLE_ID,
APPLE_APP_ID
)
const verifiedNotification: ResponseBodyV2DecodedPayload = await verifier.verifyAndDecodeNotification(signedPayload)
if (!verifiedNotification)
{
// Failure
return
}
Here is how AppStoreServerAPIClient is being used:
const appStoreServerAPIClient = new AppStoreServerAPIClient(
SIGNING_KEY,
APPLE_IAP_KEY_ID,
APPLE_IAP_ISSUER_ID,
APPLE_BUNDLE_ID,
APPLE_ENVIRONMENT
)
const statusResponse: StatusResponse = await appStoreServerAPIClient.getAllSubscriptionStatuses(originalTransactionId, [Status.ACTIVE])
In the source code for SignedDataVerifier.verifyAndDecodeNotification, I can see that it throws a VerificationException(VerificationStatus.INVALID_ENVIRONMENT) error .
So for SignedDataVerifier is it as simple as wrapping my code in a try/catch and checking that the error's status code is 21007? I'm unsure about this because if you scroll to the bottom of the linked source code file, you can see the enumeration VerificationStatus, but it's unclear if this member has a value of 21007.
The source code for AppStoreServerAPIClient only says that it throws an APIException if a response could not be processed, so I'm not too sure about how to handle this one.
I'm developing storekitV2, my app is providing the way to refund some product, and I use method below.
func beginRefundRequest(in scene: UIWindowScene) async throws -> Transaction.RefundRequestStatus
however when i call the method, the modal view presented but the view shows error with message 'cannot connect'. when I select retry button, something done with indicator and get same result.
how can I solve this problem?
I’m trying to get approval for an update to my app to transition from paid model to IAP subscription. This is my first time submitting an app with IAP. This is my current state:
App submission shows rejected
IAP subscriptions show "Development action needed"
Localization (for all subscription plans) was showing "Rejected"; I updated (only) the review notes for each subscription plan, and now the status for the localization of each subscription is "Prepare for submission" (the status for each subscription is still "Development action needed")
I'm at a loss for what needs to be done, and in what order. Sandbox testing has been working fine all along.
1 - I noticed my submitted binary was missing the "In-App Purchase" capability in Xcode (under target capabilities). Could this be the reason? Is this even needed? I am surprised that weeks of sandbox testing without the IAP capability added in Xcode has been working fine.
2- I seem to be in a chicken and egg situation,
Do I need to submit localizations first for review and how? Do I need to submit the subscriptions first for review and how?
It seems modified/new localizations need approved subscriptions, and new/modified subscriptions need an approved app with IAP support first, is this correct understanding?
As I mentioned, in the current state, I can see the app working fine with sandbox testing
Appreciate any help with step-by-step instructions to get past the review.
Is it possible to switch back to V1 if we find some issues? in sandbox and production.
I've found conflicting answers, here is the list of answers and when were they posted:
yes (not sure if only in sandbox mode), dec 2022 -> https://developer.apple.com/forums/thread/722115
no, aug 2024 -> https://forums.developer.apple.com/forums/thread/761272
yes, nov 2024 https://developer.apple.com/documentation/technotes/tn3180-reverting-app-store-server-notifications-v1
What is the right one?
Can we get v2 notifications without enabling it? Just by using this api https://developer.apple.com/documentation/appstoreserverapi/get_notification_history
Thanks for your time
Hello, I've been trying to get the sandbox environment working for in-app purchases, but so far, no luck. I can use a storekit config file to simulate purchases just fine. The item is a single consumable product.
I've checked that my product ID matches, followed the advice tendered to other forum users, created a sandbox user, all to no avail.
I've signed into the app store using my sandbox account on one phone - I can't get the "Sandbox User" option to appear on the second after attempting to make a purchase (per https://developer.apple.com/documentation/storekit/testing-in-app-purchases-with-sandbox ).
What I'm wondering is, do I need to get the in-app-purchase approved/released through App Review before I can even perform testing or something? I've signed all agreements, set up our banking information, everything seems to be in order, but I just cannot get the StoreKIt products call to return anything. ( let products = try await Product.products(for: productId) )
Is there anything else I can check? I've also checked everything here: https://forums.developer.apple.com/forums/thread/652077
Thanks!
Hi everyone,
I'm encountering an issue with StoreKit 2 and subscriptions that I hope someone can help clarify.
Here's the scenario:
A user purchases a subscription.
The user cancels the subscription in their Apple ID settings (it remains active until the end of the billing period).
While the subscription is still active, the user reopens the app and tries to repurchase the same subscription.
The problem:
The purchase() method start well a new billing flow, but once confirmed throws a StoreKitError.unknown.
Despite this error, the subscription gets re-enabled and appears active again in the currentEntitlements.
So my question is why the purchase method throw an error and how to deal with this case ?
I precise I'm currently only working in sandbox, maybe the behavior is different in production (but even in that case there is still a bug in Sandbox that should not happen).
Also in that case the Apple Server Notification API send the webhook with type DID_CHANGE_RENEWAL_STATUS (and subtype AUTO_RENEW_ENABLED), which is expected.
Thanks for help,
Gregoire.
We are facing an issue where some users send receipts with an empty 'in_app' attribute during server validation.
We are using the API described here:
https://developer.apple.com/documentation/appstorereceipts/verifyreceipt
After a purchase, we validate the receipt on the server and call finishTransaction in the app.
Most receipts are fine, but this issue affects some users.
The documentation states that an empty 'in_app' attribute means no valid purchases:
https://developer.apple.com/documentation/appstorereceipts/responsebody/receipt/in_app#discussion
Our questions are:
When is a receipt with an empty 'in_app' attribute generated?
How should we handle such receipts?
Thank you for your help.
Best regards,
i m integrate App Store Server Notifications for External Purchase Server API
The documentation says:
For external purchase tokens that are unreported after 10 days, Apple sends a server notification to your server
https://developer.apple.com/documentation/externalpurchaseserverapi#Receive-notifications-for-unreported-tokens
I have a doubt.
After I'm generating an external purchase token (sandbox), apple notify me 4 times in next 2-3h, even they say, they will notify me after 10 days for unreported tokens.
Mention: Response for notification call was 200
token_create: 13:18
notification_1: 13:58
notification_2: 14:38
notification_3: 15:08
notificaiton_4: 15:38
"notificationType": "EXTERNAL_PURCHASE_TOKEN",
"subtype": "UNREPORTED",
I have no clue what's happening
Hi,
title says it all: I have Transaction.currentEntitlements returning expired subscriptions (testing both transaction expirationDate & RenewalState).
Environment: local via .storekit file. Subscription duration is shortened for testing. Could it be the issue? The sub duration is normally 1 year.
The documentation says it should only returns active subscription (RenewalState.subscribed) or in grace period (RenewalState.inGracePeriod).
The sample code provided in https://developer.apple.com/wwdc21/10114 doesn't appear to call finish() on unverified transactions, and I haven't been able to find any documentation regarding what to do with unfinished transactions. However, Apple has always emphasized the importance of finishing transactions, and since a transaction object is provided even with the unverified state, I'd love some guidance!