StoreKit

RSS for tag

Support in-app purchases and interactions with the App Store using StoreKit.

Posts under StoreKit tag

200 Posts
Sort by:

Post

Replies

Boosts

Views

Activity

Sandbox Testing Issue: 'Submit for Review' Button Missing for Subscription IAPSubmit
Hello Developers! I am trying to run a test In-App Purchase (IAP) for a renewable subscription product in the sandbox environment. I have set up everything required for sandbox testing, but it’s unclear whether the subscription product needs to be pre-approved by Apple for testing. If approval is required, I have already added the necessary metadata, but the status still shows "Ready for Submit." Additionally, when I save the page, a "Submit for Review" button briefly appears for a fraction of a second on the subscription page before disappearing. Is this button missing, or is there a known issue with this process? This is my first time running this test, and I would appreciate any pointers or suggestions. Thanks!
0
0
19
8h
StoreKit.product.purchase returns success, but subscription does not appear in Settings
Description: We are developing an iOS app that offers only one auto-renewable subscription. Recently, a user who had not subscribed for over a year purchased the subscription again through our app. However, when they checked Settings > Subscriptions, the new subscription did not appear. Instead, only their past subscription history was visible, and there was no option to cancel the new subscription. We have verified this issue with a screenshot provided by the user. Additionally, we checked our app’s logs and confirmed that StoreKit.product.purchase successfully returned .success, indicating that the purchase was processed correctly. Code used for purchase: let result = try await product? .purchase(options: [ .appAccountToken(uuid) ]) switch result { case .success(let verificationResult): switch verificationResult { case .verified(let transaction): addPurchaseLog("verificationResult.verified.") case .unverified(_, let verificationError): addPurchaseLog("verificationResult.unverified.") case .userCancelled, .pending, .none: addPurchaseLog("verificationResult.userCancelled or .pending or .none") @unknown default: addPurchaseLog("verificationResult.unknown.") } Despite the successful purchase, the new subscription does not appear under the user's active subscriptions. Could this be a bug in StoreKit? If there are any known issues or workarounds, we would appreciate any guidance. Environment: Xcode: 16.2 iOS version: 16.0+
0
0
29
1d
Auto-renewable subscriptions does not work in production
I don't know why the subscription doesn't work in production, the message Subscription Unavailable appears. The subscription is approved in appstoreconnect. Works normally using storekit in xcode, both signing and using subscriber features. According to apple's guidance, first I do the test to validate https://buy.itunes.apple.com/verifyReceipt and then https://sandbox.itunes.apple.com/verifyReceipt. Plus I create the app-specific shared secret and put it in the code to validate the receipts. I have looked for several topics and I can not solve this problem. What do I need to do to make it work in production? I believe I did what's in the official documentation.
0
0
19
1d
External Link Account Modal language
Hi All, We are developing our app with an approved External Link Account Entitlement. During the development process (such as installing from Xcode or creating an Ad-hoc build and installing it on a phone), the open() function of the External Link Account API displays the modal our native language. The app only localized to that language. However, after uploading the app with the same configuration to TestFlight, the modal somehow appears in English instead. What could be causing this issue with the External Link Account modal? How can the open() function display the modal in another language when installed from Xcode or an Ad-hoc release build, but in English when installed from TestFlight? How can we show only our native lanugage version only to our Users? Thank you in advance
0
0
27
6d
Does in-app purchase allow dynamic amount set by user?
Hi, I have an app where user can select an amount within range 20$ - 2500$ and purchase tokens worth that amount, so if he pays 520$ he gets 520 tokens. They can then use these tokens to unlock content within the app. As far as I've seen, in-app purchase doesn't allow custom price set by user. So does this mean we can use third-party payment services? If no, how can we achieve this using in-app purchase. Thank you.
0
0
13
1w
"Bug description: We were unable to tap on 'Upgrade to Pro' button."
Hey guys, I am developing a habit tracker app right now. I know it's basic but it's my first app and I wanted an easy start but this is more of a struggle than I thought. So I am trying to publish my app fully on the App Store however everytime I do there is some kind of bug with it. But when I test it, it works perfect for me. Anyways, in this case the reviewer said when they go to the pro page of my app to purchase a pro subscription and try to press upgrade to pro nothing pops up. It pops up normally for me on my iPhone but not other devices. I wanted to make sure if this was normal or if there is something I am supposed to fix. Any help is appreciated. Thank you.
0
0
24
1w
In-App Subscription
Hi, I have a problem with StoreKit regarding in-app subscription purchases. When I upload the app to TestFlight and during the review process, it doesn’t work on a real device. However, it works fine on the simulator. I get the error: "Subscription unavailable" (see image). Does anyone know what might be causing this and how to fix it? Please help!
1
0
160
1w
【iOS18.2~18.3.2 bug】After switching sandbox accounts in Settings, the value of SKStorefront.countryCode is not synchronized
Problem Description: 1、I have two sandbox accounts from different countries. Account A is from Mainland China (CHN), and Account B is from the United States (USA). When I switch the sandbox account from Account A (CHN) to Account B (USA) in the system settings and restart the app, the value of SKPaymentQueue.defaultQueue.storefront.countryCode always returns "CHN" instead of the expected "USA". 2、This issue only occurs on iOS 18.2 and above. 3、On the same device running iOS 17.5.1, the behavior is correct. However, after upgrading the system to iOS 18.3.2, the error occurs. 4、In the sandbox environment, although SKStorefront.countryCode returns the wrong value, the currency type for Apple's in-app purchase is correct when initiating the payment. 5、The issue only exists in the sandbox environment and does not occur in the App Store-downloaded package. Demo Code: - (IBAction)clickButton:(id)sender { NSString *appStoreCountryCode3 = SKPaymentQueue.defaultQueue.storefront.countryCode; NSLog(@"%@",appStoreCountryCode3); } Demo Testing Steps and Results: 1、Sandbox Account A (China) will print "CHN". 2、Go to Settings - Developer - (at the bottom) SANDBOX APPLE ACCOUNT, and switch to another sandbox account B (USA). 3、Restart the Demo App. 4、Print results: iOS 17.5.1: "USA" ✅ → Upgrade the system of the same device to iOS 18.3.2 → "CHN" ❌ iOS 18.2.1: "CHN" ❌ iOS 18.3.1: "CHN" ❌ iOS 18.3.2: "CHN" ❌ Possible Clues: Starting with iOS 18.2, Apple changed the entry point for setting up sandbox accounts, which introduced this bug. It seems that when users switch sandbox accounts on iOS 18.2, Apple engineers forgot to notify the SKStorefront class to update the countryCode value. Before iOS 18.2: Settings - App Store - Sandbox Account iOS 18.2 and later: Settings - Developer - (at the bottom) Sandbox Account Although it doesn't affect the App Store package, it does impact our development and testing process. We hope this issue can be fixed in future versions. Thank you!
1
0
381
1d
How to handle external purchases on iOS 15.4 - 17.3 without StoreKit modal?
Hello everyone, I’m implementing external purchases in my app and I’m using ExternalPurchase.linkToExternalPurchase() on iOS 17.4+ to display the Apple modal for external purchases. However, I need to handle purchases on iOS 15.4 - 17.3, where StoreKit’s external purchase APIs do not provide tokens and return false. Questions: What is the recommended fallback for iOS 15.4 - 17.3 since the Apple modal is not available? Should I display a custom modal or directly open a WebView with the purchase page? On iOS < 15.4, since external purchase APIs are unavailable, is it acceptable to open the external purchase page directly in Safari instead of within the app? Will Apple reject the app if I use a custom modal for purchases on iOS 15.4 - 17.3 instead of the Apple-provided one on iOS 17.4+? I want to make sure my implementation complies with Apple’s guidelines while providing the best experience for users on older iOS versions. Thanks in advance for your help!
1
0
122
1d
Transitioning a freemium app to StoreKit 2
I'm currently working on transitioning to StoreKit 2. In order to see if my users are legacy users who purchased the app before I implemented an in-app purchase, I am trying to use the original purchase date for the app. Unfortunately, it's returning 0 seconds since 1970. func updateOriginalPurchaseStatus() async throws { let transaction = try await checkVerified(AppTransaction.shared) self.originalPurchaseVersion = transaction.originalAppVersion self.originalPurchaseDate = transaction.originalPurchaseDate } This is from the transaction: [3] = { key = "originalPurchaseDate" value = number (number = 0) } Currently trying to figure out when I actually purchased the app, but it might be as early as 2012. And I likely used a download code.
2
0
205
2w
[StoreKit] Runtime warning with manageSubscriptionsSheet and ObservableObject
The following runtime warning is emitted by Xcode when using the manageSubscriptionsSheet with an ObservableObject: \ContentViewModel.isPresented is isolated to the main actor. Accessing it via Binding from a different actor will cause undefined behaviors, and potential data races; This warning will become a runtime crash in a future version of SwiftUI. Publishing changes from background threads is not allowed; make sure to publish values from the main thread (via operators like receive(on:)) on model updates. This minimal sample project reproduces the issue: class ContentViewModel: ObservableObject { @Published var isPresented = false func didTapButton() { self.isPresented = true } } struct ContentView: View { @ObservedObject var viewModel: ContentViewModel var body: some View { Button("Tap me") { self.viewModel.didTapButton() } .manageSubscriptionsSheet(isPresented: self.$viewModel.isPresented) } } Reproduced on: Xcode 16.2 Xcode 16.3 beta both with a simulator and a real device. This doesn't happen when using @Observable.
0
0
136
2w
Receiving 404 Error for APNs Server Notifications When Validating signedPayload
Hi everyone, I'm experiencing an issue with APNs server notifications where I receive a 404 error when trying to validate the signedPayload from Apple's notification. Below is a sanitized version of my code: class ServerNotificationAppleController extends Controller { // URL for StoreKit keys (Sandbox environment) private $storeKitKeysUrl = 'https://api.storekit-sandbox.itunes.apple.com/inApps/v1/keys'; public function handleNotification(Request $request) { \Log::info($request); $signedPayload = $request->input('signedPayload'); if (!$signedPayload) { return response()->json(['error' => 'signedPayload not provided'], 400); } // Step 1: Create your JWT token (token creation logic can be in a separate service) $jwtToken = $this->generateAppleJWT(); // Step 2: Send a request to the StoreKit keys endpoint $response = Http::withHeaders([ 'Authorization' => 'Bearer ' . $jwtToken, ])->get($this->storeKitKeysUrl); Log::info('Apple Keys Status:', ['status' => $response->status()]); Log::info('Apple Keys Body:', ['body' => $response->body()]); if ($response->status() !== 200) { return response()->json(['error' => "Apple public keys couldn't be retrieved"], 401); } $keysData = $response->json(); // Step 3: Validate the signedPayload $validatedPayload = $this->validateSignedPayload($signedPayload, $keysData); if (!$validatedPayload) { return response()->json(['error' => 'Invalid signedPayload'], 400); } // Process the validated data as needed Log::info("Apple Purchase Data:", (array)$validatedPayload); return response()->json(['message' => 'Notification processed successfully'], 200); } private function generateAppleJWT() { // API key details (replace placeholders with actual values) $keyId = config('services.apple.key_id'); // e.g., <YOUR_KEY_ID> $issuerId = config('services.apple.issuer_id'); // e.g., <YOUR_ISSUER_ID> $privateKey = file_get_contents(storage_path(config('services.apple.private_key'))); // Set current UTC time and expiration time (20 minutes later) $nowUtc = Carbon::now('UTC'); $expirationUtc = $nowUtc->copy()->addMinutes(20); // Create the payload with UTC timestamps $payload = [ 'iss' => $issuerId, 'iat' => $nowUtc->timestamp, 'exp' => $expirationUtc->timestamp, 'aud' => 'appstoreconnect-v1', 'bid' => 'com.example.app', // Replace with your Bundle ID if necessary ]; // Generate the JWT token return JWT::encode($payload, $privateKey, 'ES256', $keyId); } private function validateSignedPayload($signedPayload, $keysData) { try { $jwkKeys = JWK::parseKeySet($keysData); return JWT::decode($signedPayload, $jwkKeys, ['RS256']); } catch (\Exception $e) { Log::error("Apple Purchase Validation Error: " . $e->getMessage()); return null; } } } I’m particularly puzzled by the fact that I receive a 404 error when trying to retrieve the public keys from the StoreKit keys endpoint. Has anyone encountered this issue or can provide insight into what might be causing the error? Any help or suggestions would be greatly appreciated. Thanks!
2
0
277
2w
App Clip Closes Before SKOverlay Can Show “Open” Button When Live Activity Is Involved
I have an App Clip that uses SKOverlay.AppClipConfiguration to install the full app. Before I added a Live Activity call (Activity.request), the user could see “Install,” then “Open.” Now, once “Get” is tapped, the Clip immediately closes—no “Open” button appears. If I remove the Live Activity code, it works again. I’ve confirmed that parent/child entitlements match, and tested via TestFlight. Is there a known issue or recommended workaround for combining SKOverlay + Live Activities in an App Clip so it doesn’t dismiss prematurely? Any insights are appreciated! Note live activity is for App Clip only.
1
0
224
3w
Revoked Non-Consumable Purchases & currentEntitlements
We use Transaction.currentEntitlements in StokeKit 2 to unlock functionality based on a Non-Consumable IAP but we have a case involving a refund that seems wrong and I am trying to understand the interation between transactionId, originalTransactionId & revocationReason. The Context: We have a universal App on macOS and iOS that offers a shared Non-Consumable IAP. For this example I have named it "app.lifetime" On macOS we use StoreKit 2 and I am calling the Transaction.currentEntitlements and Transaction.all functions. On iOS we are still using StoreKit 1. This example customer: Originally purchased "app.lifetime" on 2024-10-27 Was refunded by Apple for "app.lifetime" on 2024-10-29 Re-purchased "app.lifetime on 2025-02-24 (I have seen an email receipt of this transaction but it never shows up in Transaction data) (all the above happened on the mac via StoreKit 2) The Transactions (all lightly redacted for privacy): on macOS the following is returned from Transaction.currentEntitlements... { "appTransactionId" : "...8123", "bundleId" : "app", "currency" : "USD", "deviceVerification" : "...", "deviceVerificationNonce" : "...", "environment" : "Production", "inAppOwnershipType" : "PURCHASED", "originalPurchaseDate" : 1729997808000, "originalTransactionId" : "...9955", "price" : 1, "productId" : "app.lifetime", "purchaseDate" : 1729997808000, "quantity" : 1, "signedDate" : 1740416289102, "storefront" : "USA", "storefrontId" : "143441", "transactionId" : "...7511", "transactionReason" : "PURCHASE", "type" : "Non-Consumable" } Note in the above example the originalTransactionId & transactionId are different. Transaction.all however returns both transactions: [ { "appTransactionId" : "...8123", "bundleId" : "app", "currency" : "USD", "deviceVerification" : "...", "deviceVerificationNonce" : "...", "environment" : "Production", "inAppOwnershipType" : "PURCHASED", "originalPurchaseDate" : 1729997808000, "originalTransactionId" : "...9955", "price" : 1, "productId" : "app.lifetime", "purchaseDate" : 1729997808000, "quantity" : 1, "revocationDate" : 1730224102000, "revocationReason" : 0, "signedDate" : 1740415969925, "storefront" : "USA", "storefrontId" : "143441", "transactionId" : "...9955", "transactionReason" : "PURCHASE", "type" : "Non-Consumable" }, { "appTransactionId" : "...8123", "bundleId" : "app", "currency" : "USD", "deviceVerification" : "...", "deviceVerificationNonce" : "...", "environment" : "Production", "inAppOwnershipType" : "PURCHASED", "originalPurchaseDate" : 1729997808000, "originalTransactionId" : "...9955", "price" : 1, "productId" : "app.lifetime", "purchaseDate" : 1729997808000, "quantity" : 1, "signedDate" : 1740416289102, "storefront" : "USA", "storefrontId" : "143441", "transactionId" : "...7511", "transactionReason" : "PURCHASE", "type" : "Non-Consumable" } ] Note here that the original transaction ("...9955") includes a revocationDate and revocationReason that match the expected refund but the secondary transaction that seems to match on all other details is missing the revocation info. Looking at the iOS SK1 receipt data to compare, after a receipt refresh I see only a single transaction "...9955" which includes the cancellation info and transaction "...7511" is not present at all. The impact of this is that on iOS we are considering the purchase void but on macOS we are following currentEntitlements and consdering it still valid. Calling the inApps/v1/history/... server API with the "...7511" transactionId that is shown in the currentEntitlements response returns the "...9955" transaction with the correct revocation status but "...7511" is no returned at all. To Summarise: currentEntitlements on macOS shows transaction "...7511" as active and with an originalTransactionId of "...9955" all on macOS includes both "...7511" as active and "...9955" as revoked iOS reciept data shows only "...9955" as revoked Server API shows only "...9955" as revoked event when explicitly called with "...7511" Neither of them show a more recent purchase the same customer made for the same IAP product. My questions are: Is this a StoreKit bug or am I mis-understanding something? If it's a bug how can I work around it to ensure revoked purchases aren't still appearing in currentEntitlements? Under what conditions can StoreKit generate multiple transactionIds for the same underlying originalTransactionId? I had assumed (and the docs suggest) this only happens for subscriptions but here it is happening for a Non-Consumable IAP. Why would transactionId "...7511" only be present on macOS/SK2 and not visible at all on iOS/SK1 or API? I don't understand why the latest IAP from 2025-02-24 that the customer assures me they made (and has shown me the receipt for is not showing up in the Transactions history at all. Any ideas?
2
0
254
3w