robotmedia / RMStore

A lightweight iOS library for In-App Purchases
Apache License 2.0
2.43k stars 450 forks source link

Right way to get the last receipt data using RMStore #148

Open renatga opened 9 years ago

renatga commented 9 years ago

I'm testing in-app-purchase functionality and stuck in receipt validation step. My receipt validation is customized and based on number of checks I proceed on server-side(calling my server API) and one them is condition for transactionID uniqueness to confirm payment. So, using RMStore I'm getting receipt successfully and addPayment function return unique transaction to success block. After that I'm running verifyTransaction and call receiptURL inside success block. Unfortunately, it looks like I'm always get the same receipt using receiptURL and when I send it to my server it responds me with error that transactionID already exists in DB and transactionID is not unique. This error indicates that I send the same receipt even I do new payment. Please, note I use consumable product.

Can someone show me the right chain of calls I provide below in order I do that in my code :

  1. [RMStore productRequest] - I call it right in init function.
  2. [RMStore addPayment] - It is start of consequence I need to understand.
  3. [[[RMStore defaultStore].transactionPersistor consumeProductOfIdentifier:pID] -- Do I need to call this function at all? Apple sends me unique transactinID each time I pay and calling this function has no influence.
  4. [[[RMStore] defaultStore].receiptVerificatore verifyTransaction:transaction] -- Do I need to call transaction verification?
  5. [RMStore receiptURL] or [[NSBundle mainBundle] appStoreReceiptURL] -- Are those ways to get receiptURL equal ? Both don't work as I expect.
  6. [NSData dataWithContentOfURL:receiptURL] -- Here is the main problem I'm experiencing. After different attempts with combination of functions I mentioned upper, this method always return the receipt with the same transactionID. This makes me think that there is a cash I need to refresh before run #5, #6 to get the last receipt, but it is not clear how to do that.

So, RMStore wiki looks good, but I always expect that library will give me a chance do not fully understand what is going on under the hood. Unfortunately for RMStore I have to investigate how Apple in app purchase API works. This can be resolved if a consequence of functions calls will be provided for RMStore what I cannot find it anywhere(!)

Can someone review the list of functions I provided upper and help me with putting them in right order what let me fix my issue with getting the last receipt after the purchase I do?

justvanbloom commented 9 years ago

RMAppReceipt *receipt = [RMAppReceipt bundleReceipt];

if (receipt != nil) {

    NSDateFormatter* localDateTime = [[NSDateFormatter alloc] init];
    //NSLog(@"%@", [NSTimeZone knownTimeZoneNames]);
    [localDateTime setTimeZone:[NSTimeZone timeZoneWithName:@"Europe/Berlin"]];

    [localDateTime setDateFormat:@"yyyy.MM.dd HH:mm:ss zzz"];

    for (RMAppReceiptIAP* purchase in receipt.inAppPurchases) {

        NSString* cancellationDate = nil;

        if (purchase.cancellationDate) {

            cancellationDate = [localDateTime stringFromDate:purchase.cancellationDate];

        }

        NSLog(@"Transaction: %@: product %@, original purchase date: %@, expiration date: %@, cancellation date: %@",

              purchase.originalTransactionIdentifier,

              purchase.productIdentifier,

              [localDateTime stringFromDate:purchase.originalPurchaseDate],

              [localDateTime stringFromDate:purchase.subscriptionExpirationDate],

              cancellationDate);

    }
}
justvanbloom commented 9 years ago

RMStoreAppReceiptVerificator verificator = [RMStoreAppReceiptVerificator new]; [verificator verifyTransaction:transaction success:^(void){ //your code } failure:^(NSError error) { //alert eg smtg }]

but you need to make it accessable in the class first.

in RMStoreAppReceiptVerificator.h add

xiaosongshu commented 7 years ago

hi @justvanbloom know it's been a while since you answered, but had a qq -

if we are dealing only with auto-renewables, do we always have to loop over the receipts? given I only care if one of the three subscriptions (part of same family) are active, couldn't I technically just just look at the last receipt? (given, any upgrade or cancellation will replace the previous receipt)

justvanbloom commented 7 years ago

Just look at the last recipe? Technically yes, BUT do you know it's there? download the last recipe if there is no and verify if it is a real one and look at the buytime eg.

xiaosongshu commented 7 years ago

Sorry, I made a typo. I meant the last element in inAppPurchases in the receipt. Looks like the array is not ordered so I have to loop through all the elements. I did have one other question though, do you know if refreshing the receipt for autorenewables works just as well as restoring transactions? It's my question 3 in #203.

justvanbloom commented 7 years ago

Yep. The same way. I'll tmr at code and get you the lines. Nice Sunday.

justvanbloom commented 7 years ago

RMAppReceipt *receipt = [RMAppReceipt bundleReceipt];

if (receipt != nil) { NSDateFormatter localDateTime = [[NSDateFormatter alloc] init]; [localDateTime setTimeZone:[NSTimeZone timeZoneWithName:@"Europe/Berlin"]]; [localDateTime setDateFormat:@"yyyy.MM.dd HH:mm:ss zzz"]; RMAppReceiptIAP lastTransaction = nil; NSTimeInterval lastInterval = 0; for (RMAppReceiptIAP *iap in receipt.inAppPurchases) { NSTimeInterval thisInterval = [iap.originalPurchaseDate timeIntervalSince1970]; if (!lastTransaction || thisInterval > lastInterval) { lastTransaction = iap; lastInterval = thisInterval; } } NSLog(@"lastbuy:%@ %@",lastTransaction.productIdentifier,[localDateTime stringFromDate:lastTransaction.originalPurchaseDate]) }

xiaosongshu commented 7 years ago

Awesome, thanks for your help!

Apologies, one last question, but would you have any idea why when I call RMAppReceipt* appReceipt = [RMAppReceipt bundleReceipt]; I sometimes get a vastly different number elements in my inAppPurchases array (ie. 1 element becomes 14 elements)? It's my question 2 in the other post and I just can't understand why this is happening!

justvanbloom commented 7 years ago

Nslog the recipe and you will see :)

xiaosongshu commented 7 years ago

Haha that's what I'm doing! Is there something I should be checking for? Receipt is magically changing somehow after leaving view and segueing again. All I'm doing is RMAppReceipt* appReceipt = [RMAppReceipt bundleReceipt]; and it's somehow a different receipt...

justvanbloom commented 7 years ago

Sort it by lastone or step trough all. Depends on usage.

xiaosongshu commented 7 years ago

I think it was just a race condition, maybe because I changed the bundle id earlier. Tried it on a new phone and seems to be working so far. Here's to hoping things are good now. Thanks for your help!