SKPaymentTransactionStatePurchased called multiple times by error
Asked Answered
C

5

8

I'm making an in-app purchase, but I have a bug that I can't find where it comes from...

First of all, I have a button and when you click it, my app Request the product, catches the response, you pay, and you get the product (everything works OK). But here comes my problem. If I click the button to buy anything again, I get TWO alerts that I bought something TWO times. Even if I click for a third time, i get THREE alerts that I bought something THREE times, and four and five according to the number of times I clicked.

So it seems like some variable keeps storing the requests.. Here's my code:

This validates the product ID

- (void) validateProductIdentifiers
{ 
NSString *monedas = @" ID FROM PRODUCT ";
NSSet *product = [NSSet setWithObject:monedas];

productsRequest = [[SKProductsRequest alloc] initWithProductIdentifiers:product];

productsRequest.delegate = self;
[[SKPaymentQueue defaultQueue] addTransactionObserver:self];
[productsRequest start];
NSLog(@"STARTED REQUEST");
}

This handles the response from Apple

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


productsRequest=nil;

int found=0;

SKProduct *paraPagar;
skProducts = response.products;

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

    paraPagar = skProduct;
}
if (found==1){ 
    payment = [SKMutablePayment paymentWithProduct:paraPagar];
    payment.quantity = 1;

    [[SKPaymentQueue defaultQueue]addPayment:payment];

}else{ 

 //error (not relevant code)

}
}

Accepting payment and finishing transaction

- (void)paymentQueue:(SKPaymentQueue *)queue
updatedTransactions:(NSArray *)transactions
{



for (SKPaymentTransaction *transaction in transactions) {
    switch (transaction.transactionState) {

        case SKPaymentTransactionStatePurchased:
            NSLog(@"BOUGHT");

            [[SKPaymentQueue defaultQueue] finishTransaction:transaction];
            [self completeTransaction:transaction];


            break;
        case SKPaymentTransactionStateFailed:
            NSLog(@"FAILED");
           [[SKPaymentQueue defaultQueue] finishTransaction:transaction];

            [self failedTransaction];
            break;
        case SKPaymentTransactionStateRestored:
            NSLog(@"RESTORED");
            //[self restoreTransaction:transaction];
            [[SKPaymentQueue defaultQueue] finishTransaction:transaction];
        case SKPaymentTransactionStatePurchasing:
            NSLog(@"PURCHASING");
        default:
            break;
    }

}

}

Thank you very much for your time!

Check answered 21/11, 2013 at 13:13 Comment(0)
C
11

My problem was that the observer was being duplicated every time I clicked on the button or when I changed views.

The solution is adding a flag, to see if the observer has already been added.

static bool hasAddObserver=NO;

PAYMENT METHOD{
if (!hasAddObserver) {
    [[SKPaymentQueue defaultQueue] addTransactionObserver:self];
    hasAddObserver=YES;
}
.....
Check answered 21/11, 2013 at 22:0 Comment(1)
Per Apple's docs, "Your application should add an observer to the payment queue during application initialization." So your solution works, but even better is to start observing for transactions once as soon as your app launches.Exenterate
D
5

I had the same problem, I would click buy on an IAP and would receive multiple responses and purchases from Apple.

addTransactionObserver() was being called every time I left and came back to the view via viewDidLoad().

I needed to un-observe it in viewWillDisappear():

override func viewWillDisappear() {
  SKPaymentQueue.defaultQueue().removeTransactionObserver(self)
}

I could't use a flag to prevent multiple observers like in mursang's answer because I listened for transactions in other views.

Diatropism answered 30/10, 2015 at 7:27 Comment(1)
well, my observer is a descendant of UIViewController, as i need it to perform some UI actions in updatedTransactions: method. No way it's can be done until viewDidLoad method get called and an instance of that UIViewController created.Lobate
K
1

Are you remembering to call

[[SKPaymentQueue defaultQueue] finishTransaction:transaction];

when the purchase is completed ?

Kepner answered 21/11, 2013 at 15:21 Comment(1)
It is added in "case SKPaymentTransactionStatePurchased:" Do I need to add it somewhere else?Check
P
0

[addTransactionObserver] should be run only one time.

so you can use the singleton pattern to solve this problem.

+ (InAppPurchase *)sharedInstance
{
    static dispatch_once_t once;
    static InAppPurchase * sharedInstance;
    dispatch_once(&once, ^{
        sharedInstance = [[self alloc] initWithProductIdentifiers:nil];
    });
    return sharedInstance;
}

- (id)initWithProductIdentifiers:(NSSet *)productIdentifiers {

    if ((self = [super init])) {
        // Add self as transaction observer
        [[SKPaymentQueue defaultQueue] addTransactionObserver:self];
    }
    return self;
}
Pelagia answered 29/10, 2014 at 4:6 Comment(1)
But it can not solve this problem. you should better call it at didFinishLaunchingWithOptions like [InAppPurchase sharedInstance]; When you wanna buy something, you can call like [[InAppPurchase sharedInstance] buyProduct:(SKProduct) myProduct];Pelagia
P
0

Swift 3: According to @sponrad answer

override func viewWillDisappear(_ animated: Bool) { 
    SKPaymentQueue.default().remove(self)
}
Pinwheel answered 19/7, 2017 at 7:23 Comment(1)
Does anyone know why this is needed?! You would think we would not need this and it would be included in the transaction complete portion of the code.Uriah

© 2022 - 2024 — McMap. All rights reserved.