Hello Developer support, In one of our live application we have seen that users are purchasing weekly subscription and automatically they shifted to yearly product. Due to this we have received high revenue and also refunds afterwards. To prevent this we removed our yearly product from the sale. In App Store Connect we have seen activations for weekly product and convert to standard prices are yearly products. Also we have seen weird behavior of user getting trial for same product for three times. For developer support people I am sharing my app id - 1320373866 , and here is the video of our issue - https://drive.google.com/file/d/1DBHw8ivgql4eNoo8NC3xo5v4wgr8Oh7x/view?usp=sharing , Also attaching trial behavior screenshot.
StoreKit
RSS for tagSupport in-app purchases and interactions with the App Store using StoreKit.
Selecting any option will automatically load the page
Post
Replies
Boosts
Views
Activity
Unsure if this has been reported before, but I'm seeing a specific scenario where Apple is sending back faulty information in App Store Server Notifications.
When users have payment failures that result in the membership expiring, and then reactivate later on with a different subscription, they are given a new originalTransactionId, yet we have three cases where we receive messages about the old originalTransactionId, indicating that after the user recovers their subscription, Apple is resurrecting the old subscription and treating it as if it is active BUT ultimately decides to deactivate it.
Here is a sample screenshot of an impacted user. We only have three month and annual plans.
Timeline:
March 21st:
This user signed up for a 3 Month Plan
June 21st:
This user fails to pay
July 7th:
After our 16 day grace period, this user loses access
July 29th:
This user sees they lost access and repurchase
Augsut 19th:
Their membership is deactivated (long before their 3 Month Subscription is up)
For further verification I've also attached a record of all the payloads we've received from Apple for this user.
Please let me know if you need any more details to fix this bug OR if it has already been fixed! Thank you.
user_webhook_data_sanitized.csv
My app has in app purchase for subscriptions, available in many countries. When using Sandbox App Store accounts on TestFlight with a locale different from my own in the iOS 26 RC, I'm getting incorrect currency coming back from Product.products(for: identifiers), and so my app displays the wrong price for the locale. However, the actual Apple Pay buy sheet shows the proper currency symbol and currency amount. This did not happen on prior versions of iOS.
Is anyone else experiencing this?
Hello I am trying to add payments on my app. I have added all products I need in subscriptions. I also tried by using storekit but from my understanding testing with storekit is nothing to rely on. My app works on Android (with the same product IDs same unction call) iOS is not why is that?
The development subscription management screen for managing your sandbox account subscriptions is today showing products that belong to other apps.
Is there a known issue right now?
Created a FB with screenshots
FB FB20199893
Topic:
App & System Services
SubTopic:
StoreKit
I keep getting this error:
Provisioning profile "iOS Team Provisioning Profile: com.visuallearningaids.craftshowtracker0320" doesn't include the com.apple.developer.in-app-purchase entitlement.
I've made several different identifiers and profiles.
Thanks
Greg
Hi everyone,
I’ve been receiving App Store Server Notifications (webhooks) normally for a specific transaction. Everything was working fine — I could use the original_transaction_id to query transaction history, and transaction_id to look up specific transactions.
However, starting recently, all API calls for that transaction (both Get Transaction Info and Get Transaction History) now return not found.
• The webhooks used to arrive normally for this transaction.
• I am sure I’m querying the correct environment (production vs sandbox).
• The IDs I’m using are exactly the ones from the last webhook payload.
• I haven’t changed my integration code.
My questions:
1. Under what conditions does Apple’s API return “not found” for original_transaction_id that previously worked?
2. Can Apple actually purge transaction records (in production)?
3. Could this happen due to refunds, revocations, or other account actions?
4. Is this expected behavior, or should I file a DTS (Technical Support Incident)?
Any insight would be appreciated.
Thanks in advance!
Hello,
Our app has been released in Korea with an external payment feature.
The function that must display the required modal for external payments is as follows:
let result = try await ExternalPurchase.presentNoticeSheet()
guard result != .cancelled else {
return
}
However, while testing in the development environment, the API returns a result of continuedWithExternalPurchaseToken(token: String), but in the version distributed through the App Store, it returns cancelled.
We would like to know how we can receive the continuedWithExternalPurchaseToken result in the App Store version.
Thank you for your guidance. If anyone here has experienced a similar issue or found a solution, I would greatly appreciate your insights as well.
iOS Storekit2 Appstore production environment, some user feedback in app purchase faliure, What our log records is StoreKitError.unknown,please How to solve problem, thanks
We’re encountering a problem with StoreKit in the sandbox environment. During an SKProductsRequest, some or all of the product identifiers provided are being returned in the invalidProductIdentifiers array of the SKProductsResponse, despite being valid and already approved.
This behavior began occurring since last 2 days only and appears to be intermittent—in some cases, a product identifier that initially fails will succeed after one or two retries, without any changes on our end.
What we've verified:
All product identifiers are correctly configured and approved in App Store Connect.
No recent changes have been made to our product configuration.
The same product identifiers have previously worked without issue.
We would appreciate any assistance in identifying the root cause of this behavior. Please also let us know if there is a known issue currently affecting StoreKit sandbox services.
Thank you for your support.
Hello,
Is anyone else seeing Purchase.PurchaseResult.UserCancelled, despite a successful transaction?
I had a user notify me today that he:
Attempted a purchase
Entered payment credentials
Was asked to opt in to email subscription notifications
Opted In
Was shown my app's "User Canceled Purchase" UI
Attempted to repurchase
Was alerted that he was "Already Subscribed"
I have adjusted my code to check Transaction.currentEntitlements on receiving a .userCancelled result, to avoid this in the future. Is this logically sound?
Here is my code - please let me know if you see any issues:
func purchase(product: Product, userId: String) async throws -> StoreKit.Transaction {
let purchaseUUID = UUID()
let options: Set<Product.PurchaseOption> = [.appAccountToken(purchaseUUID)]
let result = try await product.purchase(options: options)
switch result {
case .success(let verification):
guard case .verified(let tx) = verification else {
throw PurchaseError.verificationFailed // Show Error UI
}
return try await processVerified(tx)
case .userCancelled:
for await result in Transaction.currentEntitlements {
if case .verified(let tx) = result, tx.productID == product.id, tx.revocationDate == nil {
return try await processVerified(tx)
}
}
throw PurchaseError.cancelled // Show User Cancelled UI
case .pending:
throw PurchaseError.pending // Show Pending UI
@unknown default:
throw PurchaseError.unknown // Show Error UI
}
}
@MainActor
func processVerified(_ transaction: StoreKit.Transaction) async throws -> StoreKit.Transaction {
let id = String(transaction.id)
if await transactionCache.contains(id) {
await transaction.finish()
return transaction // Show Success UI
}
let (ok, error) = await notifyServer(transaction)
guard ok else {
throw error ?? PurchaseError.serverFailure(nil) // Show Error UI
}
await transaction.finish()
await transactionCache.insert(id)
return transaction // Show Success UI
}
The only place the "User Cancelled Purchase" UI is displayed on my app is after the one instance of "throw PurchaseError.cancelled" above.
This happened in Production, but I have also seen userCancelled happen unexpectedly in Sandbox.
Thank you for your time and help.
I’m implementing subscriptions and running tests, and I noticed a behavior I’d like to confirm.
Plans in the app
Basic — Monthly
Basic — Annual
Premium — Monthly
Premium — Annual
Test environment
Sandbox (where ~1 day ≈ under 1 minute of real time)
steps
Start Basic (Monthly) using an introductory offer (free trial).
Create a crossgrade to Basic (Annual) (scheduled/queued).
3.After receiving a RENEWAL App Store Server Notification indicating the plan will move from trial to paid Basic (Annual), but before the trial actually expires, upgrade the user to Premium (Monthly).
Observed behavior (Sandbox) & questions
Even though there is still up to ~1 day of trial remaining (≈ under 1 minute in Sandbox), upgrading to Premium (Monthly) immediately ends the trial and activates the paid Premium plan right away.
Will this same behavior occur in Production?
If yes, is this the expected/acceptable behavior when upgrading during an active trial after a pending crossgrade?
Note: If we upgrade to Premium before the RENEWAL notification arrives, the remaining trial time is carried over in our tests.
In this flow, we see a RENEWAL notification for Basic (Annual) (moving from trial → paid), but then the user immediately upgrades to Premium (Monthly) and the trial ends at that moment.
In Production, would the charge for Basic (Annual) be refunded automatically since the user effectively switches to Premium immediately (and Basic Annual does not remain active)?
In Sandbox there’s no real charge, but I want to ensure we won’t see a situation in Production where Basic (Annual) is billed and not refunded, even though the subscription effectively moved to Premium right away.
Thanks in advance!
iOS Storekit2 Appstore production environment, some user feedback in app purchase faliure, What our log records is StoreKitError.unknown,please How to solve problem, thanks
I set both production and sandbox App Store Notification server.
In sandbox, my server can receive all kinds of app store notification, including subscription and non-consumable in-app-purchase.
But in production, my server only receive subscription notification.
I can see some non-consumable in-app-purchase done in log, but the server didn't receive expected notification.
Anyone know why?
I really need the notification cause I need to know who made refund.
When trying to login with sandbox account in my simulator, nothing happens, it just become inactive for few seconds and then it become active again prompting that I can login, again and again.
Hello, I’m trying to change the business model of my app to in-app subscriptions. My goal is to ensure that previous users who paid for the app have access to all premium content seamlessly, without even noticing any changes.
I’ve tried using RevenueCat for this, but I’m not entirely sure it’s working as expected. I would like to use RevenueCat to manage subscriptions, so I’m attempting a hybrid model. On the first launch of the updated app, the plan is to validate the app receipts, extract the originalAppVersion, and store it in a variable. If the original version is lower than the latest paid version, the isPremium variable is set to true, and this status propagates throughout the app. For users with versions equal to or higher than the latest paid version, RevenueCat will handle the subscription status—checking if a subscription is active and determining whether to display the paywall for premium features.
In a sandbox environment, it seems to work fine, but I’ve often encountered situations where the receipt doesn’t exist. I haven’t found a way to test this behavior properly in production. For example, I uploaded the app to TestFlight, but it doesn’t validate the actual transaction for a previously purchased version of the app. Correct me if I’m wrong, but it seems TestFlight doesn’t confirm whether I installed or purchased a paid version of the app.
I need to be 100% sure that users who previously paid for the app won’t face any issues with this migration. Is there any method to verify this behavior in a production-like scenario that I might not be aware of?
I’m sharing the code here to see if you can confirm that it will work as intended or suggest any necessary adjustments.
func fetchAppReceipt(completion: @escaping (Bool) -> Void) {
// Check if the receipt URL exists
guard let receiptURL = Bundle.main.appStoreReceiptURL else {
print("Receipt URL not found.")
requestReceiptRefresh(completion: completion)
return
}
// Check if the receipt file exists at the given path
if !FileManager.default.fileExists(atPath: receiptURL.path) {
print("The receipt does not exist at the specified location. Attempting to fetch a new receipt...")
requestReceiptRefresh(completion: completion)
return
}
do {
// Read the receipt data from the file
let receiptData = try Data(contentsOf: receiptURL)
let receiptString = receiptData.base64EncodedString()
print("Receipt found and encoded in base64: \(receiptString.prefix(50))...")
completion(true)
} catch {
// Handle errors while reading the receipt
print("Error reading the receipt: \(error.localizedDescription). Attempting to fetch a new receipt...")
requestReceiptRefresh(completion: completion)
}
}
func validateAppReceipt(completion: @escaping (Bool) -> Void) {
print("Starting receipt validation...")
guard let receiptURL = Bundle.main.appStoreReceiptURL else {
print("Receipt not found on the device.")
requestReceiptRefresh(completion: completion)
completion(false)
return
}
print("Receipt found at URL: \(receiptURL.absoluteString)")
do {
let receiptData = try Data(contentsOf: receiptURL, options: .alwaysMapped)
print(receiptData)
let receiptString = receiptData.base64EncodedString(options: [])
print("Receipt encoded in base64: \(receiptString.prefix(50))...")
let request = [
"receipt-data": receiptString,
"password": "c8bc9070bf174a8a8df108ef6b8d2ae3" // Shared Secret
]
print("Request prepared for Apple's validation server.")
guard let url = URL(string: "https://buy.itunes.apple.com/verifyReceipt") else {
print("Error: Invalid URL for Apple's validation server.")
completion(false)
return
}
print("Validation URL: \(url.absoluteString)")
var urlRequest = URLRequest(url: url)
urlRequest.httpMethod = "POST"
urlRequest.httpBody = try? JSONSerialization.data(withJSONObject: request)
URLSession.shared.dataTask(with: urlRequest) { data, response, error in
if let error = error {
print("Error sending the request: \(error.localizedDescription)")
completion(false)
return
}
guard let data = data else {
print("No response received from Apple's server.")
completion(false)
return
}
print("Response received from Apple's server.")
do {
if let json = try JSONSerialization.jsonObject(with: data) as? [String: Any] {
print("Response JSON: \(json)")
// Verify original_application_version
if let receipt = json["receipt"] as? [String: Any],
let appVersion = receipt["original_application_version"] as? String {
print("Original application version found: \(appVersion)")
// Save the version in @AppStorage
savedOriginalVersion = appVersion
print("Original version saved in AppStorage: \(appVersion)")
if let appVersionNumber = Double(appVersion), appVersionNumber < 1.62 {
print("Original version is less than 1.62. User considered premium.")
isFirstLaunch = true
completion(true)
} else {
print("Original version is not less than 1.62. User is not premium.")
completion(false)
}
} else {
print("Could not find the original application version in the receipt.")
completion(false)
}
} else {
print("Error parsing the response JSON.")
completion(false)
}
} catch {
print("Error processing the JSON response: \(error.localizedDescription)")
completion(false)
}
}.resume()
} catch {
print("Error reading the receipt: \(error.localizedDescription)")
requestReceiptRefresh(completion: completion)
completion(false)
}
}
Some of these functions might seem redundant, but they are intended to double-check and ensure that the user is not a previous user. Is there any way to be certain that this will work when the app is downloaded from the App Store?
Thanks in advance!
The majority of our sandbox calls to verifyReceipt end in an ETIMEDOUT error. This is making it very difficult to verify our purchase flow for our pending release. We have not yet migrated to StoreKit 2 and still rely on this API endpoint.
The Apple API status page reports no issues.
Is anyone else encountering this?
I have an Expo React Native application and I am using react-native-iap library. I have setup the URL for sandbox and production url to receive notifications when a something happens regarding subscriptions. I am not able to receive those notifications when I simulate the purchase on the Simulator using Xcode.
I then have used the node library app-store-server-library to mock a test notification and I am receiving the test notification when I call requestTestNotification method.
below is the react-native code I am using:
import {useEffect, useState} from "react";
import {
initConnection,
Sku,
Subscription,
useIAP,
SubscriptionIOS,
requestSubscription,
PurchaseError,
clearTransactionIOS
} from "react-native-iap";
import styles from "@/screens/IAP/IAPStyles";
import CustomText from "@/components/CustomText";
import Heading from "@/components/Heading";
import subscriptionsProducts from "@/utilities/products";
function IAPScreen() {
const [isPurchasing, setIsPurchasing] = useState<boolean>(false);
const [loading, setLoading] = useState<boolean>(false);
const { subscriptions, currentPurchase, finishTransaction, getSubscriptions } = useIAP();
function RenderProduct({ item } : { item: Subscription }) {
const iosSubscription: SubscriptionIOS = item as SubscriptionIOS;
return(
<View style={styles.productCard}>
<CustomText
customStyles={styles.productTitle}
text={iosSubscription.title}
size={"medium"}
/>
<CustomText
customStyles={styles.productDescription}
text={iosSubscription.description}
size={"small"}
/>
<CustomText
customStyles={styles.productPrice}
text={iosSubscription.localizedPrice}
size={"small"}
/>
<Button
title="Purchase"
disabled={isPurchasing}
onPress={
() => HandlePurchase(iosSubscription.productId)
}
/>
</View>
);
}
async function HandlePurchase(sku: Sku) {
try {
setIsPurchasing(true);
await requestSubscription({
sku,
andDangerouslyFinishTransactionAutomaticallyIOS: false
});
}
catch (error: any) {
Alert.alert("Error", "Failed to purchase: " + error.message);
}
finally {
setIsPurchasing(false);
}
}
useEffect(() => {
setLoading(true);
console.log(`[${new Date().toISOString()}] Initializing IAP connection...`);
const setupIAP = async () => {
try {
const result = await initConnection();
console.log(`[${new Date().toISOString()}] IAP connection initialized:`, result);
await clearTransactionIOS();
await getSubscriptions({
skus: subscriptionsProducts
});
}
catch (error: any) {
Alert.alert("Error", "Failed to load products: " + error.message);
}
finally {
setLoading(false);
}
};
setupIAP()
.finally(() => setLoading(false));
}, []);
useEffect(() => {
const checkCurrentPurchase = async () => {
try {
if(currentPurchase?.productId) {
console.log("Current purchase: ", currentPurchase);
console.log("Transaction Id: ", currentPurchase.transactionId);
await finishTransaction({
purchase: currentPurchase,
isConsumable: false,
});
}
}
catch (error) {
if(error instanceof PurchaseError) {
console.log("Purchase error: ", error);
}
else {
Alert.alert("Error", "Failed to finish transaction: " + error);
}
}
}
if(currentPurchase) {
console.log("Finishing current purchase.");
checkCurrentPurchase()
.catch(error => Alert.alert("Error", "Failed to finish transaction: " + error.message));
}
}, [currentPurchase, finishTransaction]);
return(
<View style={styles.mainContainer}>
<Heading
text={"Packages & Subscriptions"}
type={"h2"}
customStyles={styles.header}
/>
{
loading ?
<ActivityIndicator size="large" /> :
subscriptions.length > 0 ?
(
<>
<FlatList
data={subscriptions}
renderItem={RenderProduct}
keyExtractor={(item) => item.productId}
/>
</>
) :
(
<CustomText
customStyles={styles.productDescription}
text={"No available products."}
size={"small"}
/>
)
}
</View>
);
}
export default IAPScreen;
I am using a store kit file where I just edited the scheme of application to use that store kit file.
I would be really thankful If you can help me in this matter.
Product.SubscriptionInfo.isEligibleForIntroOffer(for: "21340582")
In the production environment, I have already used the intro offer for this group, but this method still returns true
Sorry but I'm not good at English.
Is the originalTransactionId received for the same subscription by the same user the same in Store Kit v1 and Store Kit 2?
Also, will the originalTransactionId be the same even if the subscription price is changed?