when I have not verified payment method and we choose UPI then pop up shows Purchase In Progress and it shows notification for UPI app payment for apps like Gpay, Paytm. But when we approve or decline this payment StoreKit methods not called for success or failure and we have to kill app to refetch the status and stop the loader.
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
I'm considering developing an app where users can create their own subscription plans by freely setting their prices, similar to YouTube's membership feature.
I understand that in-app purchases must be used to unlock features within the app. With that in mind, I searched for APIs to enable this functionality but couldn't find relevant information.
When I contacted Apple directly, they mentioned that they couldn't provide specific answers unless the app is under review.
If anyone has knowledge about the following points, I would greatly appreciate your response:
Is it possible to implement a feature similar to YouTube's membership using in-app purchase APIs? If it's not feasible with in-app purchases, is it allowed to use external payment services like Stripe?
My IAPs no longer display for one of my apps. I'm not sure where to start to troubleshoot. Any ideas?
Topic:
App & System Services
SubTopic:
StoreKit
Hello Apple Developer Team,
We're experiencing consistent IAP approval rejections under Guideline 2.1, despite successful TestFlight verification. Here's our detailed situation:
Environment
StoreKit 1 implementation
Tested on iOS 18.5 or 18.6 devices
Sandbox environment works perfectly
Verification Steps Taken
✅ Confirmed all Product IDs match App Store Connect exactly
✅ Validated 10+ successful TestFlight transactions (attached screenshot samples)
✅ Verified banking/tax agreements are active
Objective-C Code (StoreKit1 Implementation)
- (void)buyProductId:(NSString *)pid AndSetGameOrderID:(NSString *)orderID{
if([SKPaymentQueue canMakePayments]){
if (!hasAddObserver) {
[[SKPaymentQueue defaultQueue] addTransactionObserver:_neo];
hasAddObserver = YES;
}
self.neoOrderID = orderID;
[[NSUserDefaults standardUserDefaults] setValue:orderID forKey:Pay_OrderId_Key];
self.productID = pid;
NSArray * product = [[NSArray alloc]initWithObjects:self.productID, nil];
NSSet * nsset = [NSSet setWithArray:product];
SKProductsRequest * request = [[SKProductsRequest alloc]initWithProductIdentifiers:nsset];
request.delegate = self;
[request start];
}else{
NSString * Err = @"Pembelian tidak diizinkan. Silakan aktifkan perizinan di pengaturan";
// UnitySendMessage("GameManager", "IAPPurchaseFailed", [Err UTF8String]);
return;
}
}
- (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response
{
NSArray * product = response.products;
if ([product count] == 0)
{
[[SKPaymentQueue defaultQueue]removeTransactionObserver:_neo];
hasAddObserver = NO;
NSString * Err = [NSString stringWithFormat:@"Err = 01, Item tidak ditemukan %@",self.productID];
// UnitySendMessage("GameManager", "IAPPurchaseFailed", [Err UTF8String]);
return;
}
SKProduct * p = nil;
for (SKProduct * pro in product)
{
if ([pro.productIdentifier isEqualToString:self.productID]){
p = pro;
}else{
[request cancel];
[[SKPaymentQueue defaultQueue]removeTransactionObserver:_neo];
hasAddObserver = NO;
NSString * Err = [NSString stringWithFormat:@"Err = 02, %@",self.productID];
// UnitySendMessage("GameManager", "IAPPurchaseFailed", [Err UTF8String]);
return;
}
}
SKMutablePayment * mPayment = [SKMutablePayment paymentWithProduct:p];
mPayment.applicationUsername = [NSString stringWithFormat:@"%@",self.neoOrderID];
if(!hasAddObserver){
[[SKPaymentQueue defaultQueue] addTransactionObserver:_neo];
hasAddObserver = YES;
}
[[SKPaymentQueue defaultQueue] addPayment:mPayment];
}
- (void)request:(SKRequest *)request didFailWithError:(NSError *)error{
[[SKPaymentQueue defaultQueue]removeTransactionObserver:_neo];
hasAddObserver = NO;
NSString * Err = [NSString stringWithFormat:@"Err = 0%ld %@", (long)error.code, self.productID];
// UnitySendMessage("GameManager", "IAPPurchaseFailed", [Err UTF8String]);
}
- (void)requestDidFinish:(SKRequest *)request{
}
- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transaction{
for(SKPaymentTransaction *tran in transaction){
if (SKPaymentTransactionStatePurchased == tran.transactionState){
[self completeTransaction:tran];
}else if(SKPaymentTransactionStateFailed == tran.transactionState){
[self failedTransaction:tran];
}
}
}
- (void)failedTransaction: (SKPaymentTransaction *)transaction
{
NSString * detail = [NSString stringWithFormat:@"%ld",(long)transaction.error.code];
// UnitySendMessage("GameManager", "IAPPurchaseFailed", [detail UTF8String]);
[[SKPaymentQueue defaultQueue]removeTransactionObserver:_neo];
hasAddObserver = NO;
[[SKPaymentQueue defaultQueue] finishTransaction: transaction];
}
- (void)completeTransaction:(SKPaymentTransaction *)transaction{
NSMutableDictionary * mdic = [NSMutableDictionary dictionary];
NSString * productIdentifier = transaction.payment.productIdentifier;
NSData * _recep = nil;
NSString * _receipt = @"";
if (floor(NSFoundationVersionNumber) <= NSFoundationVersionNumber_iOS_6_1) {
_recep = transaction.transactionReceipt;
_receipt = [[NSString alloc]initWithData:_recep encoding:NSUTF8StringEncoding];
} else {
_recep = [NSData dataWithContentsOfURL:[[NSBundle mainBundle] appStoreReceiptURL]];
_receipt = [_recep base64EncodedStringWithOptions:NSDataBase64EncodingEndLineWithLineFeed];
}
NSString * gameOrderid = [transaction payment].applicationUsername;
if (gameOrderid == nil) {
gameOrderid = [[NSUserDefaults standardUserDefaults] objectForKey:Pay_OrderId_Key];
}
if(_receipt != nil && gameOrderid != nil){
mdic[@"orderid"] = gameOrderid;
mdic[@"productid"] = productIdentifier;
mdic[@"receipt"] = _receipt;
}else{
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
return;
}
NSData * data = [NSJSONSerialization dataWithJSONObject:mdic options:kNilOptions error:nil];
NSString * jsonString = [[NSString alloc]initWithData:data encoding:NSUTF8StringEncoding];
if (hasAddObserver) {
[[SKPaymentQueue defaultQueue] removeTransactionObserver:_neo];
hasAddObserver = NO;
}
// UnitySendMessage("GameManager", "IAPPurchaseSuecess", [jsonString UTF8String]);
[self verifyReceipt:_recep completion:^(BOOL success, NSDictionary *response) {
if (success) {
NSLog(@"verify success");
// [[SKPaymentQueue defaultQueue] finishTransaction:transaction];
[self verifySuecessDelTransactions];
}
}];
}
- (void)paymentQueueRestoreCompletedTransactionsFinished:(SKPaymentQueue *)queue {
for(SKPaymentTransaction *tran in queue.transactions){
if (SKPaymentTransactionStatePurchased == tran.transactionState){
[self completeTransaction:tran];
}
}
}
- (void)verifySuecessDelTransactions{
SKPaymentQueue *paymentQueue = [SKPaymentQueue defaultQueue];
NSArray<SKPaymentTransaction *> *transactions = paymentQueue.transactions;
if (transactions.count == 0) {
return;
}
for (SKPaymentTransaction *transaction in transactions) {
if (transaction.transactionState == SKPaymentTransactionStatePurchased ||
transaction.transactionState == SKPaymentTransactionStateRestored) {
[paymentQueue finishTransaction:transaction];
}
}
}
Hi, I've migrated my app to another developer account more than half a year ago, but I'm still receiving a few transaction payments in the old developer account, which currently has no app.
The payment date shown in the report is last month.
I'm wondering how could this happen. Is it possible for users to initiate a transaction half a year ago and only successfully pay it now?
Hello, I’m trying to change my business model within the app, and following Apple’s documentation guidelines HERE I created this task in the main view of the app. It seems to work perfectly in the simulator, on physical devices, and on TestFlight. However, after releasing it to production and uploading the new version to the App Store, it doesn’t work, and all users, whether new or existing, are asked to subscribe. In the console, it appears to retrieve the transactions correctly, but in production, I’m not sure how to view the console or see what it’s retrieving.
Here the sandbox receipt I obtained
AppTransaction.shared obtained: {
"applicationVersion" : "1",
"bundleId" : "com.anestesiaIB.Drugs-Infusion-Calc",
"deviceVerification" : "6M0Nnw14nSEOBVTPE\/\/EfnWSwLm7LFSlrpFEwxgH74SBHp5dSzBEm896Uvo42mwr",
"deviceVerificationNonce" : "8a8238c0-0aee-41e6-bfb0-1cfc52b70fb6",
"originalApplicationVersion" : "1.0",
"originalPurchaseDate" : 1375340400000,
"receiptCreationDate" : 1737577840917,
"receiptType" : "Sandbox",
"requestDate" : 1737577840917
}
This are the processing log while verified the receipt
New business model change: 1.7
Original versionéis components: ["1", "0"]
Major version: 1, Minor version: 0
This user is premium. Original version: 1.0
This is my task...
.task {
do {
let shared = try await AppTransaction.shared
if case .verified(let appTransaction) = shared {
let newBusinessModelVersion = (1, 7) // Representado como (major, minor)
let versionComponents = appTransaction.originalAppVersion.split(separator: ".")
if let majorVersion = versionComponents.first.flatMap({ Int($0) }),
let minorVersion = versionComponents.dropFirst().first.flatMap({ Int($0) }) {
if (majorVersion, minorVersion) < newBusinessModelVersion {
self.premiumStatus.isPremium = true
isPremium = true
} else {
let customerInfo = try await Purchases.shared.customerInfo()
self.premiumStatus.isPremium = customerInfo.entitlements["premium"]?.isActive == true
isPremium = self.premiumStatus.isPremium
}
} else {
print("Error: obteining version components")
}
} else {
print("Not verified")
}
} catch {
print("Error processing transaction: \(error.localizedDescription)")
}
}
Hi everyone,
We’ve encountered an issue in some of our games where IAP purchases made using UPI are going into a pending state. Since these purchases are for consumable items, the rewards are not granted at the time of purchase.
Even after the transactions are eventually confirmed, the rewards still aren't received.
We tested this with two separate UPI transactions, and both resulted in the same pending state issue.
Interestingly, when we tried making a purchase using Apple Wallet afterward, the transaction completed successfully on the first attempt, without any pending state.
This issue seems specific to UPI transactions.
Could anyone help us understand why this is happening or if there’s a recommended way to handle such cases?
Thanks in advance!
Topic:
App & System Services
SubTopic:
StoreKit
Hello,
I have a question regarding the App Store Server API's getTransactionInfo endpoint.
Previously, the official documentation for getTransactionInfo mentioned that:
“This endpoint doesn’t support an app transaction. To get information about an app transaction, decode the signed app transaction received from the device.”
However, as of June 2025, I can no longer find this sentence in the current documentation (link). Now, the docs state that all in-app purchase transaction IDs are supported (consumable, non-consumable, auto-renewable subscriptions, etc.).
But in practice, when I call getTransactionInfo with an AppTransactionId (extracted from a signed App Transaction JWS), I receive this error: apiErrorCode: 4000048
“Invalid request. App transactions aren’t supported by this endpoint.”
Is this endpoint supposed to support AppTransactionId now, or is the restriction still in place (but not mentioned in the docs)?
Is there any official statement about when this restriction was added/removed?
Can you clarify if only in-app purchase transaction IDs (and not AppTransactionIds) are supported for this endpoint, or has the policy changed recently?
Any clarification or historical context would be greatly appreciated.
Additionally, I would like to know about the behavior of an App Transaction in the event of a refund.
If a user receives a refund for the app itself (not an in-app purchase), how can changes to the AppTransaction be detected?
Does the App Store Server Notification v2 provide notifications for app-level refunds, or are such events only visible by decoding the latest App Transaction JWS on the device?
Is there any way to receive app-level refund information server-side, or must we always rely on the device to provide the updated signed app transaction?
Any clarification on this refund flow and notification coverage would also be appreciated.
Thank you!
https://developer.apple.com/documentation/appstoreserverapi/get-v2-history-_transactionid
I would like to inquire about the detailed triggers for updating receipts in this API specification.
Recently, I was using this API with sort=DESCENDING&revoked=false to retrieve the expiration date of the most recent receipt and determine the subscription status. However, for some reason, an old receipt with an earlier expiration date appeared as the first receipt, and I would like to know the reason for this.
Can you provide information on what specific events or actions trigger the updating of receipts in this API?
Also, regarding https://developer.apple.com/documentation/appstoreserverapi/status, will statuses 3 and 4 not be returned in the response unless the billing grace period is enabled in the App Store?
We are currently integrating In-App Purchases for our app and have configured App Store Server Notifications (v2) in the Sandbox environment.
During testing, we observed the following issue:
When a transaction is cancelled, declined, or pending (e.g., Ask to Buy flows or authorization pending),
No App Store Server Notification is sent to our webhook endpoint.
We only receive webhook events where the status is "purchased".
This becomes a critical problem for us because our backend must accurately track transaction states including failed and pending purchases, especially for wallet top-up use cases.
Additionally, we tried mocking failed transactions (via Xcode local environment and turning off In-App Purchases from Developer Settings) to simulate a technical failure scenario.
Even in these cases, no webhook notification was received when the purchase failed server-side.
Is it expected behavior in Sandbox that only successful transactions ("purchased") trigger webhooks?
Are failed or pending transactions suppressed in Sandbox intentionally?
Will webhook behavior be different in Production (i.e., will we receive webhook notifications for failures there)?
Is there any extra configuration or entitlement needed to fully test failure scenarios via webhooks in Sandbox?
Topic:
App & System Services
SubTopic:
StoreKit
Tags:
In-App Purchase
App Store Server Notifications
I'm working on adding a single Non-Consumable In-App purchase to my app. Essentially a "try before you buy" type thing. Limited functionality unless the app is purchased.
I am currently testing this using Xcode and the Manage StoreKit Transactions window. So far most everything appears to be working except for declined pending transactions.
If I set Ask to Buy to Enabled, the Ask Permission (for parent or guardian) dialog appears. After pressing the Ask button, I see a transaction listed as Pending Approval. If I Approve the transaction, then my app is notified and all is well.
However, if I Decline the transaction then my app is not notified. Is that normal?
Also, how do I (i.e. the app) know that there is a pending transaction?
Topic:
App & System Services
SubTopic:
StoreKit
I have three questions about verify receipt
I use this api (https://buy.itunes.apple.com/verifyReceipt)to verify receipt is success or not. But since last month, this interface has started to return an error(21002).
I see this document (https://developer.apple.com/documentation/appstorereceipts/verifyreceipt) say its Deprecated.
My question is, is the error suddenly returned recently because the interface has been deprecated or for some other reason? (I haven't modified my code about this recently)
2. I can not understand this document: (https://developer.apple.com/documentation/appstorereceipts/validating_receipts_on_the_device)
Does this mean that in the new version, as long as the app returns a payment success (purchaseDetails.status == PurchaseStatus.purchased), the payment is guaranteed to be successful, and my server does not need to request payment result verification from Apple's server?
3. I try to use this (https://github.com/apple/app-store-server-library-java) to get TransactionInfo, but I dont konw to get Transaction status to know is success or not.
my java server code :
AppStoreServerAPIClient client = new AppStoreServerAPIClient(encodedKey, keyId, issuerId, bundleId, environment);
TransactionInfoResponse response = client.getTransactionInfo(transactionId);
(bug i can note get transaction status, how do i konw this Transaction is success or not)
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
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.
Strange issue with currency display in subscription products
Hi everyone,
I'm facing a strange issue in my app where I use a subscription-based in-app purchase model.
The products I created in App Store Connect are all in "Approved" status.
I've tested with both RevenueCat and StoreKit, but the result is the same.
Here are the products being loaded:
Product loaded: weekly_product_id
Display name: Weekly Pro
Description: Weekly Pro Subscription
Price: ₺229,99
Product loaded: annual_product_id
Display name: Annual Pro
Description: Annual Pro Subscription
Price: ₺1.799,99
Even though I can see the correct prices and currency (Turkish Lira) in the Xcode debug console, on my real device the currency appears as Philippine Peso, as shown in the attached screenshot.
Interestingly, in the iOS simulator, it's displayed in USD.
I've double-checked and my device's region settings are set to Turkey.
Any ideas on what could be causing this? And more importantly, how can I fix it?
Thanks in advance!
I have created a Python app and built it with pyinstaller and codesigned everything. Now I want to Sandbox test it. In my appstore connect account i have created a subscriptions id. I read that if I am logged out from the AppStore and have codesigned my .app file with a Developer Certificate i should be able to run the app on my local mac and when i click on the "Buy" button it should connect to my app store connect setup. I have implemented StoreKit in my app and use a storekit_bridge to combine the .swift code with my python app.
However when i run the app. I get this: "25-07-24 21:01:12,557 - FEC - WARNING - StoreKit: fetchProducts returned empty result
2025-07-24 21:01:12,557 - FEC - INFO - StoreKit fetch_products returned: {"products": []}
2025-07-24 21:01:12,557 - FEC - ERROR - StoreKit: Failed to parse product info: No products returned from JSON"
And no login screen appears where I should be able to enter my Sandbox email adress and password.
Anyone here who has experience with a Python app combined with In App Purchases? Hope someone can help me out with this.
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?
hI, I’m looking for some clarity on the rules around in app purchasing, so I play a game with in app purchases that you have random odds of getting items out of loot boxes, they split the loot into sections, so featured, mega rare, rare and common. for each of these sections they give a % chance that they show of what chance you have to get an item out of that section, I.e featured is 1.24% and common is 74% ect.
my question is should they then have to display the odds of what each item has in those sections as well or is it good enough just to have the sections with % chance? So each section could have 10 items in, should those items have a % next to as well?
any advice you can give with this would be appreciated, if needs be I can send picture examples of what I’m explaining
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!
My Mac app fails to open for some users with the error:
"ABC.app does not support the latest receipt validation requirements."
I assume this is due to the update of the App Store receipt signing intermediate certificate with one that uses the SHA-256 algorithm.
I cannot reproduce this myself and I have trouble figuring out how to address this issue.
Below is the code that decrypts the receipt and verifies its signature.
How does this code need to be updated to support the new signing certificate?
Thanks a lot in advance!
inline static void CheckBundleSignature(void)
{
NSURL *bundleURL = [[NSBundle mainBundle] bundleURL];
SecStaticCodeRef staticCode = NULL;
OSStatus status = SecStaticCodeCreateWithPath((__bridge CFURLRef)bundleURL, kSecCSDefaultFlags, &staticCode);
if (status != errSecSuccess) {
[NSException raise:@"MacAppStore Receipt Validation Error" format:@"Failed to validate bundle signature: Create a static code", nil];
}
NSString *requirementText = @"anchor apple generic";
SecRequirementRef requirement = NULL;
status = SecRequirementCreateWithString((__bridge CFStringRef)requirementText, kSecCSDefaultFlags, &requirement);
if (status != errSecSuccess) {
if (staticCode) CFRelease(staticCode);
[NSException raise:@"MacAppStore Receipt Validation Error" format:@"Failed to validate bundle signature: Create a requirement", nil];
}
status = SecStaticCodeCheckValidity(staticCode, kSecCSDefaultFlags, requirement);
if (status != errSecSuccess) {
if (staticCode) CFRelease(staticCode);
if (requirement) CFRelease(requirement);
[NSException raise:@"MacAppStore Receipt Validation Error" format:@"Failed to validate bundle signature: Check the static code validity", nil];
}
if (staticCode) CFRelease(staticCode);
if (requirement) CFRelease(requirement);
}
static NSData *DecodeReceiptData(NSData *receiptData)
{
CMSDecoderRef decoder = NULL;
SecPolicyRef policyRef = NULL;
SecTrustRef trustRef = NULL;
@try {
OSStatus status = CMSDecoderCreate(&decoder);
if (status) {
[NSException raise:@"MacAppStore Receipt Validation Error" format:@"Failed to decode receipt data: Create a decoder", nil];
}
status = CMSDecoderUpdateMessage(decoder, receiptData.bytes, receiptData.length);
if (status) {
[NSException raise:@"MacAppStore Receipt Validation Error" format:@"Failed to decode receipt data: Update message", nil];
}
status = CMSDecoderFinalizeMessage(decoder);
if (status) {
[NSException raise:@"MacAppStore Receipt Validation Error" format:@"Failed to decode receipt data: Finalize message", nil];
}
NSData *ret = nil;
CFDataRef dataRef = NULL;
status = CMSDecoderCopyContent(decoder, &dataRef);
if (status) {
[NSException raise:@"MacAppStore Receipt Validation Error" format:@"Failed to decode receipt data: Get decrypted content", nil];
}
ret = [NSData dataWithData:(__bridge NSData *)dataRef];
CFRelease(dataRef);
size_t numSigners;
status = CMSDecoderGetNumSigners(decoder, &numSigners);
if (status) {
[NSException raise:@"MacAppStore Receipt Validation Error" format:@"Failed to check receipt signature: Get singer count", nil];
}
if (numSigners == 0) {
[NSException raise:@"MacAppStore Receipt Validation Error" format:@"Failed to check receipt signature: No signer found", nil];
}
policyRef = SecPolicyCreateBasicX509();
CMSSignerStatus signerStatus;
OSStatus certVerifyResult;
status = CMSDecoderCopySignerStatus(decoder, 0, policyRef, TRUE, &signerStatus, &trustRef, &certVerifyResult);
if (status) {
[NSException raise:@"MacAppStore Receipt Validation Error" format:@"Failed to check receipt signature: Get signer status", nil];
}
if (signerStatus != kCMSSignerValid) {
[NSException raise:@"MacAppStore Receipt Validation Error" format:@"Failed to check receipt signature: No valid signer", nil];
}
return ret;
} @catch (NSException *e) {
@throw e;
} @finally {
if (policyRef) CFRelease(policyRef);
if (trustRef) CFRelease(trustRef);
if (decoder) CFRelease(decoder);
}
}