Hello!
How can I set appAccountToken when I'm using the new SwiftUI view SubscriptionStoreView for subscription?
Previously I was able to set it as a purchase option here https://developer.apple.com/documentation/storekit/product/purchase(options:) but I don't see purchase options with SubscriptionStoreView.
Thank you,
sendai
StoreKit
RSS for tagSupport in-app purchases and interactions with the App Store using StoreKit.
Posts under StoreKit tag
200 Posts
Sort by:
Post
Replies
Boosts
Views
Activity
Some of my users reported they can not completed the purchase .
According to the logs and screen captures . Their purchase progress's last status are "purchasing"
func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) {
....
....
case .purchasing:
// 处理正在购买的情况
//print("购买中");
AppDelegate.log.debug("paymentQueue purchasing");
LoadingAlert.shared.setText(text: "购买中".localized())
....
After this ,It neither entered any error branch nor prompted the user to confirm the purchase or enter a password, but simply stopped here. There are no other purchase-related logs, and the program is still running normally.
At the same time, other users are able to complete their purchases without any issues. However, there have been 4-5 users recently who reported problems with purchasing. What could be the possible reasons?
In my local environment, I repeated the test many times, including using sandbox users from different regions and real Apple IDs, and everything worked fine.
//
// Payment.swift
// RadialMenu
//
// Created by pat on 2023/6/26.
//
import Foundation
import StoreKit
class Payment:NSObject,SKProductsRequestDelegate,SKPaymentTransactionObserver{
func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) {
for transaction in transactions {
AppDelegate.log.debug("paymentQueue transaction product = \(transaction.payment.productIdentifier) state = \(transaction.transactionState)");
//let productID = transaction.payment.productIdentifier
switch transaction.transactionState {
case .purchased,.restored:
if(transaction.transactionState == .purchased){
AppDelegate.log.debug("paymentQueue purchased");
LoadingAlert.shared.setText(text: "已购买".localized())
}
if(transaction.transactionState == .restored){
LoadingAlert.shared.setText(text: "已恢复".localized())
AppDelegate.log.debug("paymentQueue restored");
}
//}
break;
case .failed:
AppDelegate.log.debug("paymentQueue failed ");
if let error = transaction.error as? NSError {
// 获取错误代码和描述
let errorCode = error.code
let errorDescription = error.localizedDescription
AppDelegate.log.debug("paymentQueue Transaction failed with error code: \(errorCode), description: \(errorDescription)")
}
queue.finishTransaction(transaction)
LoadingAlert.shared.hideModal();
// 处理购买失败的情况
// 提供错误信息给用户
//print("购买失败");
alertRetry();
break;
case .deferred:
AppDelegate.log.debug("paymentQueue deferred");
LoadingAlert.shared.setText(text: "购买延迟".localized())
// 处理交易延迟的情况(仅限家庭共享)
break;
case .purchasing:
// 处理正在购买的情况
//print("购买中");
AppDelegate.log.debug("paymentQueue purchasing");
LoadingAlert.shared.setText(text: "购买中".localized())
break;
@unknown default:
AppDelegate.log.debug("paymentQueue nknown default\(transaction.transactionState)");
break
}
}
}
I am trying to implement in-app purchases in Apple TV.
I added a "non-consumable" product and started testing in Sandbox, but it did not work properly.
While I am trying to fetch the product from the appstore, it won't give any responses like success or failure.
So that our app gets rejected in the App Store.
Please provide me the steps to implement in-app purhcase in Apple tvos using Swift.
Note: The same code is working fine in iOS.
I am currently using StoreKit2 to set up the in-app purchase subscription flow, and I have already configured the subscription products in App Connect. I created a StoreKit Configuration file in Xcode and used it in the scheme. However, after completing the purchase, the transaction.jsonRepresentation data returns a transactionId of 0. After checking the documentation, I found that I need to disable the StoreKit Configuration and enable Sandbox Testing. But after disabling the StoreKit Configuration, I can't retrieve the real product data using Product.products(for: productIds). I can confirm that the ProductId I provided is real and matches the data configured in App Connect. Could you please help me identify the issue? Thank you
I have a subscription group with two individual subscriptions configured but when trying to load the SubscriptionStoreView I get the error:
"Subscription Unavailable: The subscription is unavailable in the current storefront."
When I try to load the ProductView, it appears to be stuck in a loading screen. I am running the app on a device that is signed into a sandbox account. Any guidance would be greatly appreciated. Thank you.
My question is simple, I do not have much experience in writing swift code, I am only doing it to create a small executable that I can call from my python application which completes Subcription Management.
I was hoping someone with more experience could point out my flaws along with giving me tips on how to verify that the check is working for my applicaiton. Any inight is appreciated, thank you.
import Foundation
import StoreKit
class SubscriptionValidator {
static func getReceiptURL() -> URL? {
guard let appStoreReceiptURL = Bundle.main.appStoreReceiptURL else {
print("No receipt found.")
return nil
}
return appStoreReceiptURL
}
static func validateReceipt() -> Bool {
guard let receiptURL = getReceiptURL(),
let receiptData = try? Data(contentsOf: receiptURL) else {
print("Could not read receipt.")
return false
}
let receiptString = receiptData.base64EncodedString()
let validationResult = sendReceiptToApple(receiptString: receiptString)
return validationResult
}
static func sendReceiptToApple(receiptString: String) -> Bool {
let isSandbox = Bundle.main.appStoreReceiptURL?.lastPathComponent == "sandboxReceipt"
let urlString = isSandbox ? "https://sandbox.itunes.apple.com/verifyReceipt" : "https://buy.itunes.apple.com/verifyReceipt"
let url = URL(string: urlString)!
let requestData: [String: Any] = [
"receipt-data": receiptString,
"password": "0b7f88907b77443997838c72be52f5fc"
]
guard let requestBody = try? JSONSerialization.data(withJSONObject: requestData) else {
print("Error creating request body.")
return false
}
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.httpBody = requestBody
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
let semaphore = DispatchSemaphore(value: 0)
var isValid = false
let task = URLSession.shared.dataTask(with: request) { data, response, error in
guard let data = data, error == nil,
let jsonResponse = try? JSONSerialization.jsonObject(with: data) as? [String: Any],
let status = jsonResponse["status"] as? Int else {
print("Receipt validation failed.")
semaphore.signal()
return
}
if status == 0, let receipt = jsonResponse["receipt"] as? [String: Any],
let inApp = receipt["in_app"] as? [[String: Any]] {
for purchase in inApp {
if let expiresDateMS = purchase["expires_date_ms"] as? String,
let expiresDate = Double(expiresDateMS) {
let expiryDate = Date(timeIntervalSince1970: expiresDate / 1000.0)
if expiryDate > Date() {
isValid = true
}
}
}
}
semaphore.signal()
}
task.resume()
semaphore.wait()
return isValid
}
}
I am currently testing my in app subscription via sandbox. I am able to make the purchase and verify it, but it will not auto renew. The sandbox account is flagged as being subscribed, so it can't be purchased again, but I don't actually get the auto renewal.
I did notice that randomly on app boot up, I'll get a bunch of the backlogged auto renewals come in, but they are never actually sent to me when the 3 minute expiration is finished.
This is on macOS, so I am not able to actually look at and manage the sandbox subscriptions. It seems like that's only a thing for iOS. Is this just a behavior with the sandbox environment or will this behavior also happen with legitimate App Store?
The code I use is below:
@MainActor
func updateCustomerProductStatus() async {
var purchasedSubscriptions: [Product] = []
for await result in Transaction.currentEntitlements {
do {
let transaction = try checkVerified(result)
switch transaction.productType {
case .autoRenewable:
if let subscription = subscriptions.first(where: { $0.id == transaction.productID}) {
purchasedSubscriptions.append(subscription)
}
default:
break
}
} catch {
print("catching \(error)")
}
}
init() {
subscriptions = []
updateListenerTask = listenForTransactions()
Task {
await requestProducts()
await updateCustomerProductStatus()
}
}
deinit {
updateListenerTask?.cancel()
}
func listenForTransactions() -> Task<Void, Error> {
return Task.detached {
// Iterate through any transactions that don't come from a direct call to `purchase()`.
for await result in Transaction.updates {
do {
let transaction = try self.checkVerified(result)
// Deliver products to the user.
await self.updateCustomerProductStatus()
// Always finish a transaction.
await transaction.finish()
} catch {
// StoreKit has a transaction that fails verification. Don't deliver content to the user.
print("Transaction failed verification.")
}
}
}
}
As you can see in the screenshot, the verification popups that appear when making a StoreKit purchase cut off the buttons. When typing the code into the input field, the window will also flicker and stutter with random view refreshes. Is this something I can configure/change? It's not a very pleasant experience for making an in app purchase.
I have implemented subscription renewals in my app and now want to integrate a referral system. Each user should receive a unique referral code that they can share with others. When a new user signs up using a referral code, both the referrer and the referred user should receive a bonus.
What is the best approach to implementing this feature efficiently?
In developing a new MacOS app in Xcode I set up a Storekit configuration file so I could test 2 non-consumable purchases locally. I've been successfully testing them for the past couple of weeks, but suddenly yesterday I found I was unable to make purchases. I can successfully fetch products, but when I try to purchase either of them, I get the following error:
Error handling payment sheet request: Error
Domain=NSCocoaErrorDomain Code=4099 "The connection to service
created from an endpoint was invalidated from this process." UserInfo=
{NSDebugDescription=The connection to service created from an
endpoint was invalidated from this process.}
Purchase did not return a transaction: Error Domain=ASDErrorDomain
Code=5115 "Received failure in response from Xcode" UserInfo=
{NSDebugDescription=Received failure in response from Xcode,
NSUnderlyingError=0x600002d44ae0 {Error
Domain=NSCocoaErrorDomain Code=4099 "The connection to service
created from an endpoint was invalidated from this process." UserInfo=
{AMSDescription=An unknown error occurred. Please try again.,
AMSURL=http://localhost:51482/WebObjects/MZBuy.woa/wa/inAppBuy,
NSDebugDescription=The connection to service created from an
endpoint was invalidated from this process., AMSStatusCode=200,
AMSServerPayload={
"app-list" = (
);
dialog = {
cancelButtonString = Cancel;
defaultButton = Buy;
explanation = "Do you want to buy one App Registration for $2.99?\n\n[Environment: Xcode]";
initialCheckboxValue = 1;
"m-allowed" = 0;
message = "Confirm Your In-App Purchase";
okButtonAction = {
buyParams = "bid=com.airlinemates.backup&bvrs=1.4&offerName=EIBREG&quantity=1&deviceVerification=5084f98e-ab99-5846-827e-048d00d9fac3";
itemName = EIBREG;
kind = Buy;
};
okButtonString = Buy;
paymentSheetInfo = {
caseControl = true;
confirmationTitle = Pay;
countryCode = US;
currency = USD;
designVersion = 2;
displayPrice = "$2.99";
flexList = (
{
value = (
{
style = priceMain;
value = "$2.99";
},
{
style = priceSub;
value = "One-time charge";
}
);
},
{
header = "$null";
value = "For testing purposes only. You will not be charged for confirming this purchase.";
}
);
price = "2.99";
requestor = AppStore;
salableIcon = "http://localhost:53078/StoreKit/AppIcon?bid=com.airlinemates.backup";
salableIconType = app;
salableInfo = (
"App Registration %%image_0%%",
backup,
"In-App Purchase"
);
styles = (
{
bold = true;
name = priceMain;
size = large;
},
{
color = gray;
name = priceSub;
},
{
bold = true;
name = priceMainSpaceBefore;
size = large;
spacingBefore = medium;
}
);
title = {
type = text;
value = Xcode;
};
};
};
"download-queue-item-count" = 0;
dsid = 17322632127;
failureType = 5115;
jingleAction = inAppBuy;
jingleDocType = inAppSuccess;
pings = (
);
}}}}
I've Googled & can't find any reference to this specific error, or even anything that points me in a direction to find the root cause. It's very strange because I haven't made any changes to the code & I hadn't changed the configuration file prior to this error appearing. I've since deleted the configuration file & created a new one - but it's still not working. If I create a transaction in Storekit transaction manager, the app picks it up as having been purchased - so the issue is only isolated to purchases initiated from the app.
If I stop using the configuration file when I run the app, it works fine through sandbox testing the real items in App Store Connect.
I am trying to test this simulated Error.
The issue is, I can't get this to trigger through the simulatedError function.
Error will always end up as an unknown error
Example code snippet:
@available(iOS 17.0, *)
func testPurchase_InvalidQuantity() async throws {
// Arrange
testSession.clearTransactions()
testSession.resetToDefaultState()
let productID = "consumable_1"
try await testSession.setSimulatedError(.purchase(.invalidQuantity), forAPI: .purchase)
guard let product = await fetchProduct(identifier: productID) else {
XCTFail("Failed to fetch test product")
return
}
let option = Product.PurchaseOption.quantity(4)
let result = await manager.purchase(product: product, options: option)
switch result {
case .success:
XCTFail("Expected failure due to invalid quantity")
case .failure(let error):
print("Received error: \(error.localizedDescription)")
switch error {
case .purchaseError(let purchaseError):
XCTAssertEqual(purchaseError.code, StoreKitPurchaseError.invalidQuantity.code)
default:
XCTFail("Unexpected error: \(error)")
}
}
}
In the above code snippet, I have an Unexpected Error.
But if i remove try await testSession.setSimulatedError(.purchase(.invalidQuantity), forAPI: .purchase) I will receive a XCTFail in the success of my result.
So when I set the quantity to a -1, only then can I correctly receive an invalidQuantity.
Does anyone know why the try await testSession.setSimulatedError(.purchase(.invalidQuantity), forAPI: .purchase) would fail to work as directed? I have tests for all the generic errors for loadProducts API and the simulatedError works great for them
I have developed an app that I had been testing on the hardware device with the developer profile signed builds, I had setup a CloudKit container in development mode and also had tested with Production mode and they are working as expected. I have also tested storekit auto renewal subscriptions using Storekit Config file and all of that is working on the hardware device with the developer profile signed builds.
Now comes the Fun Part, I want to use the Distribution profile to test the app for production readiness, I had created a distribution profile and had set that up in the Release under target of the app in Xcode, I have also created sandbox tester account (which is showing inactive even after 7 days - though I am also logged in with this sandbox tester account on a hardware device and under developer setting it shows as a sandbox tester account)
All the subscriptions are showing Ready to Submit in the App Store Connect.
I need help understand this whole flow, how to ensure I can test CloudKit and storekit for production readiness and then publish my app for the review.
Thank you.
I am using a credit system and would like to know if it's possible to charge users for the full weekly subscription if they upgrade to a yearly plan after using all their weekly credits within one day. My goal is to ensure that users are billed for the entire weekly period in such cases. Is this compliant with Apple's App Store policies?
We are in the process of implementing promotional offers for auto-renewable subscriptions in our app using StoreKit 2.
For testing, we use a sandbox user alongside a new user on our platform. I can successfully purchase an Introductory Offer through the app. Once the user is eligible for a Promotional Offer (based on a previous purchase), we retrieve the Promotional Offer identifier and signature from our backend and display the offer.
After initiating the purchase and having the user enter their Sandbox password, the transaction is added to the Payment Queue. However, it fails with the following error:
Purchase failed error: invalidOfferSignature
Additionally, the error returned is:
"Purchase did not return a transaction: Error Domain=ASDServerErrorDomain Code=3903 "Unable to Purchase" UserInfo={NSLocalizedFailureReason=Unable to Purchase, client-environment-type=Sandbox, AMSServerErrorCode=3903, storefront-country-code=IND}"
We are using StoreKit 2 APIs for this process.
Has anyone encountered this issue when working with StoreKit 2, or found a solution to resolve it?
Hello
I'm developing a React Native application and I added IAP (like subscriptions) to my app. For it I user react-native-iap, I suppose It's the most common library to integrate in-app purchases.
So, I created 3 subs on App Store Connect and tested it on the iOS simulator. Firstly I was receiving the empty array instead of subs data, but when I have been add StoreKit to my project I became to receive an appropriate subs data with all corresponding information. Moreover, I could successfully subscribe on them.
But, when I released app on the TestFlight for internal testing I forced with the familiar issue, I received an empty array.
In conclusion, on the debug version everything works (subscriptions data returns), but on the release I can't to receive the same result.
Hello everyone,
I’m hoping someone might help with auto-renewable subscription validation in Apple’s Sandbox environment. Here’s the situation:
My Setup:
I’ve configured three auto-renewable subscriptions in App Store Connect and generated an In-App Purchase key (with the correct Issuer ID and Key ID). (I also tried the App Store Connect API Keys)
I’m using Apple’s App Store Server API v2 endpoints (GET /inApps/v2/subscriptions/{originalTransactionId}/latest) to fetch the latest subscription status.
I’ve created several Sandbox test users (with fresh email addresses), signed out of old test accounts on my devices, and tested purchasing subscriptions anew.
What Works:
I am receiving valid Server Notifications from Apple (e.g. SUBSCRIBED, DID_RENEW) with the correct environment: "Sandbox" field.
My JWT generation appears to be correct because I’m no longer receiving 401 errors—only 404. That suggests Apple accepts the key and credentials.
My fallback logic attempts production first; if it sees a 404 or 410, it switches over to the Sandbox endpoint. This is exactly what Apple’s documentation recommends.
The Problem:
Whenever I query GET https://api.storekit-sandbox.itunes.apple.com/inApps/v2/subscriptions/{originalTransactionId}/latest using the originalTransactionId from Apple’s own Server Notification, Apple returns a 404 (indicating it can’t find that subscription).
This happens even though the subscription is active in Sandbox (I see notifications arriving for it).
I’ve tried adding a brief waiting period (2 seconds) before calling the Sandbox endpoint, but it consistently returns 404. I’ve also tried multiple retries over a longer timeframe without success.
I tested multiple fresh Sandbox test users, ensuring each one was signed in to the device’s App Store. After each new purchase, I still get the same 404.
Additional Checks:
These are definitely auto-renewable subscriptions, not non-renewing or consumable products.
I also tried calling GET /inApps/v2/subscriptions/{transactionId}/latest but I still see 404.
I tried everything mentioned above in production as I said to no avail:
GET https://api.storekit.itunes.apple.com/inApps/v2/subscriptions/{originalTransactionId}/latest
Hi all,
I am adding the following StoreKit 2 code to my app, and I don't see anything in Apple's documentation that explains the unverified case. When is that case exercised? Is it when someone has tampered with the app receipt? Or is it for more mundane things like poor network connectivity?
// Apple's docstring on `shared` states:
// If your app fails to get an AppTransaction by accessing the shared property, see refresh().
// Source: https://developer.apple.com/documentation/storekit/apptransaction/shared
var appTransaction: VerificationResult<AppTransaction>?
do {
appTransaction = try await AppTransaction.shared
} catch {
appTransaction = try? await AppTransaction.refresh()
}
guard let appTransaction = appTransaction else {
AppLogger.error("Couldn't get the app store transaction")
return false
}
switch appTransaction {
case .unverified(appTransaction, verificationError):
// For what reasons should I expect this branch to be entered in production?
return await inspectAppTransaction(appTransaction, verifiedByApple: false)
case .verified(let appTransaction):
return await inspectAppTransaction(appTransaction, verifiedByApple: true)
}
Thank you,
Lou
Hi, I've some issues on my application about purchases.
My app is available for iOS and tvOS.
Some part of my code is the same on both target (my purchase code is one of them).
The issue is:
Some users can't do a purchase on Apple TV (but not all and I can't reproduce)
Some users purchased on a device and are not considered as pro on the other device (logged with same apple account). Again it's not all.
Here is my store code
In all the illustrations of win-back offers, I see an example of "Get 3 months off, then $X/month", as seen below.
First, I'm not exactly clear how each configuration translates into an actual offer in practice:
If I want to offer 3 months off on an annual subscription, ie. only if the user pays for the annual offer (basically a 25% discount), is that possible?
If I set a "Free" type, of 3 months, I guess that would allow the user to cancel before paying for the annual, correct?
If I set a "Pay up front" type, with a 25% discount, how would that show up to the user on the App Store?
Secondly, is eligibility to an offer determined by the user elapsing on the same subscription or any subscription in the same subscription group?
Thank you
When simulating a Storekit error like an invalid device verification or others of that type, should we finish a failed transaction? When I test with a storekit configuration file, all failed transactions persist after every restart. The Apple-provided sample code for storekit 2 has transactions finished only when they are successful.