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];
}
}
}