RMStore Receipt Verification Failure Flow

741 Views Asked by At

I am using RMStore to verify receipts. As a note, I am not using RMStore for the actual purchase portion. The process is successfully dealing with success and failure in terms of throwing errors and not delivering content if the receipt is invalid. I purposefully changed the bundle to force a failure as a test. My question though is with the failure process and the confirmation Apple sends.

The issue is that while this process does detect the failure to verify and therefore does prevent the content from being sent to the user, Apple still afterwards comes back with a dialog box about the purchase being successful. The good news is that the purchase isn't successful and the content isn't delivered, but I would prefer that this dialog box from Apple not show as it will create confusion.

Here is my implementation of the check. For now I am just testing the failure scenario before doing more within the failure block.

- (void)completeTransaction:(SKPaymentTransaction *)transaction {
    NSLog(@"completeTransaction...");

    RMStoreAppReceiptVerificator *verifyReceipt = [[RMStoreAppReceiptVerificator alloc]init];

    [verifyReceipt verifyTransaction:transaction success:^{
        [self provideContentForProductIdentifier:transaction.payment.productIdentifier];
        [[SKPaymentQueue defaultQueue] finishTransaction:transaction];
    }failure:^(NSError *error){
        NSLog(@"failure to verify: %@",error.description);
    }];
}

Is there a way within the failure block to halt the process at Apple that creates their success dialog or do I need to perform this check at an earlier stage?

Update:

In looking further at this, the method above is being called by the state SKPaymentTransactionStatePurchased The definition of that state per Apple is:

"The App Store successfully processed payment. Your application should provide the content the user purchased."

This tells me that it is likely too late to prevent the dialog. There is an earlier state, however, I would think the receipt verification has to come after purchase but before delivery of content (otherwise there would not be a purchase to verify). So is this just a matter of having to deal with the conflicting message or am I missing something?

Update 2: Adding some more methods per the request in comments

@interface IAPHelper () <SKProductsRequestDelegate, SKPaymentTransactionObserver>
@end

@implementation IAPHelper
{
    SKProductsRequest * _productsRequest;
    RequestProductsCompletionHandler _completionHandler;

    NSSet * _productIdentifiers;
    NSMutableSet * _purchasedProductIdentifiers;
    NSDictionary *_mappingDict;
}

- (id)initWithProductIdentifiers:(NSSet *)productIdentifiers andMappings:(NSDictionary *)mappingDict
{

    if ((self = [super init])) {

        // Store product identifiers & mappings
        _productIdentifiers = productIdentifiers;
        _mappingDict = mappingDict;

        // Add self as transaction observer
        [[SKPaymentQueue defaultQueue] addTransactionObserver:self];

    }
    return self;

}

- (void)requestProductsWithCompletionHandler:(RequestProductsCompletionHandler)completionHandler {


    // 1
    _completionHandler = [completionHandler copy];

    // 2
    _productsRequest = [[SKProductsRequest alloc] initWithProductIdentifiers:_productIdentifiers];
    _productsRequest.delegate = self;
    [_productsRequest start];

}

- (BOOL)productPurchased:(NSString *)productIdentifier {
    return [_purchasedProductIdentifiers containsObject:productIdentifier];
}

- (void)buyProduct:(SKProduct *)product {

    NSLog(@"Buying %@...", product.productIdentifier);

    SKPayment * payment = [SKPayment paymentWithProduct:product];
    [[SKPaymentQueue defaultQueue] addPayment:payment];

}

#pragma mark - SKProductsRequestDelegate

- (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response {

    NSLog(@"Loaded list of products...");
    _productsRequest = nil;

    NSArray * skProducts = response.products;
    for (SKProduct * skProduct in skProducts) {
        NSLog(@"Found product: %@ %@ %0.2f",
              skProduct.productIdentifier,
              skProduct.localizedTitle,
              skProduct.price.floatValue);
    }

    if (_completionHandler)
    {
        _completionHandler(YES, skProducts);
        _completionHandler = nil;
    }
}

- (void)request:(SKRequest *)request didFailWithError:(NSError *)error {

    NSLog(@"Failed to load list of products.");
    _productsRequest = nil;

    if (_completionHandler)
    {
        _completionHandler(NO, nil);
        _completionHandler = nil;
    }
}

Here is the specific method that calls completeTransaction

- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions
{
    for (SKPaymentTransaction * transaction in transactions) {
        switch (transaction.transactionState)
        {
            case SKPaymentTransactionStatePurchased:
                [self completeTransaction:transaction];
                break;
            case SKPaymentTransactionStateFailed:
                [self failedTransaction:transaction];
                break;
            case SKPaymentTransactionStateRestored:
                [self restoreTransaction:transaction];
            default:
                break;
        }
    };
}
1

There are 1 best solutions below

1
On

We established in the comments that this question doesn't have anything to do with RMStore.

You're not testing a real fraud scenario so it doesn't matter if Apple shows an alert.

This would involve either using a fake receipt, or sending fake calls to the Store Kit transaction observer. In neither of those cases you would get the alert.

When using a valid transaction to simulate a failure scenario you can't expect Apple to consider the transaction invalid as well. There is no API to tell Apple that a transaction is fraudulent. You can only finish a transaction, or not.