[StoreKit1] IAP Works in TestFlight but Fails During App Review (2.1 Rejection)

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

SKProductsRequest * request = [[SKProductsRequest alloc]initWithProductIdentifiers:nsset];

    request.delegate = self;
    [request start];

From Fetching product information from the App Store > Request product information: Keep a strong reference to the request object; otherwise, the system might deallocate the request before it can complete.

var request: SKProductsRequest!

func validate(productIdentifiers: [String]) {
     let productIdentifiers = Set(productIdentifiers)
     request = SKProductsRequest(productIdentifiers: productIdentifiers)
     request.delegate = self 
     request.start()
}

Be sure to follow the instructions in Setting up the transaction observer for the payment queue.

[StoreKit1] IAP Works in TestFlight but Fails During App Review (2.1 Rejection)
 
 
Q