Eugene Alexeev Eugene Alexeev - 1 month ago 11
iOS Question

Purchasing Apple store products cause "Purchased" state at the start

Right now I'm struggling with the next problem. I've made simple IAPManager for buying things from AppStore in my application, but the problem is - when I'm trying to buy the product, application calls

updatedTransactions
method with transaction state of
Purchased
right before showing the alert if I want to buy that thing or not.

It happens only when I'm trying to buy something right before application starts. It won't happen again till I reload the application. Here's my IAPManager code:

- (id)init
{
self = [super init];
if (self) {

NSURL *receiptURL = [[NSBundle mainBundle] appStoreReceiptURL];
_receipt = receiptURL.absoluteString;
NSLog(@"Receipt = %@", _receipt);
}

return self;
}

- (void)purchaseSmallCreditPackWithCompletion:(IAPManagerBlock)completionBlock failure:(IAPManagerBlock)failureBlock
{
[self makePurchaseWithId:iap_SmallCreditPack completion:completionBlock failure:failureBlock];
}

- (void)purchaseMiddleCreditPackWithCompletion:(IAPManagerBlock)completionBlock failure:(IAPManagerBlock)failureBlock
{
[self makePurchaseWithId:iap_MiddleCreditPack completion:completionBlock failure:failureBlock];
}

- (void)purchaseLargeCreditPackWithCompletion:(IAPManagerBlock)completionBlock failure:(IAPManagerBlock)failureBlock
{
[self makePurchaseWithId:iap_LargeCreditPack completion:completionBlock failure:failureBlock];
}

- (void)makePurchaseWithId:(NSString *)idStr completion:(IAPManagerBlock)completionBlock failure:(IAPManagerBlock)failureBlock
{
if ([SKPaymentQueue canMakePayments]) {

_completionBlock = completionBlock;
_failureBlock = failureBlock;

SKProductsRequest *productsRequest = [[SKProductsRequest alloc] initWithProductIdentifiers:[NSSet setWithObject:idStr]];
productsRequest.delegate = self;
[productsRequest start];
}
else
{
NSLog(@"This user can't make any payments");
}
}

- (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response
{
if (response.products.count == 0) {
NSLog(@"There're no any products whatsoever");
return;
}

SKProduct *product = [response.products firstObject];
[self purchase:product];
}

- (void)purchase:(SKProduct *)product
{
SKPayment *payment = [SKPayment paymentWithProduct:product];

[[SKPaymentQueue defaultQueue] addTransactionObserver:self];
[[SKPaymentQueue defaultQueue] addPayment:payment];
}

- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray<SKPaymentTransaction *> *)transactions
{
SKPaymentTransaction *transaction = [transactions firstObject];

switch (transaction.transactionState) {
case SKPaymentTransactionStatePurchasing:

NSLog(@"Purchasing %@...", transaction.payment.productIdentifier);

break;
case SKPaymentTransactionStateFailed:
{
NSLog(@"Failed to purchase %@", transaction.payment.productIdentifier);

if (_failureBlock) {
_failureBlock();
}

[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
}
break;
case SKPaymentTransactionStateRestored:
{
//just in case, we don't need to restore anything so far
if (_completionBlock) {
_completionBlock();
}

[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
}
break;
case SKPaymentTransactionStatePurchased:
{

NSLog(@"Purchased %@", transaction.description);
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];

NSURL *receiptURL = [[NSBundle mainBundle] appStoreReceiptURL];
NSData *receiptData = [NSData dataWithContentsOfURL:receiptURL];
NSString *strReceipt = [receiptData base64EncodedStringWithOptions:0];
//NSLog(@"strReceipt = %@", strReceipt);

}
break;
default:
break;
}
}

- (void)paymentQueue:(SKPaymentQueue *)queue removedTransactions:(NSArray<SKPaymentTransaction *> *)transactions
{
NSLog(@"Removed Transactions");

[[SKPaymentQueue defaultQueue] removeTransactionObserver:self];
}

Answer

I think your problem is related with the observer. As I see you are adding the transaction observer every time you try to buy a product and remove it when a transaction (any) is removed from the transactions queue.

In cases when your transaction is not completed and your app is closed you will receive the notification about the completion of the transaction when you open the app. So you will not receive this notification and you will miss it.

I suggest you to add the observer in the init method and don't remove it for the reason I detailed before. Also I suggest you to create an instance of your IAP class when you open your app (didFinishLaunchingWithOptions:) to set the transactions observer and receive these delayed notifications of your transactions.

One more thing... You should manage your array of transactions and not just the first one.

You should create a for loop to deal with them like this

for (SKPaymentTransaction *transaction in transactions) {
     switch (transaction.transactionState) {
          case SKPaymentTransactionStatePurchased: {
            [[SKPaymentQueue defaultQueue] finishTransaction:transaction];
            break;
          }
          case SKPaymentTransactionStateFailed:
            [[SKPaymentQueue defaultQueue] finishTransaction:transaction];
            break;
          case SKPaymentTransactionStateRestored:
            [[SKPaymentQueue defaultQueue] finishTransaction:transaction];
            break;
          case SKPaymentTransactionStatePurchasing:
            break;
          case SKPaymentTransactionStateDeferred:
            break;
          default:
            break;
    }
};