youknowone / itunes-iap

Apple iTunes In-app purchase verification tool
http://itunes-iap.readthedocs.io
Other
136 stars 50 forks source link

IndexError: list index out of range #49

Open aaksarin opened 6 years ago

aaksarin commented 6 years ago

Hello. We got this problem with itunes-iap 2.5

[2017-11-08 15:00:20,934] [ERROR] views.py:3308 - root: list index out of range
Traceback (most recent call last):
  File "/home/django/atcontrol/releases/1510061996/mayak/api_v3/views.py", line 3243, in purchase_ios
    log.info(res.receipt.last_in_app)
  File "/home/django/atcontrol/releases/1510061996/venv/local/lib/python2.7/site-packages/itunesiap/receipt.py", line 245, in last_in_app   
    self.in_app, key=lambda x: x['original_purchase_date_ms'])[-1]
IndexError: list index out of range

part of view from our django rest framework api with problem line of code

@api_view(['POST'])
def purchase_ios(request):
    log.info('purchase_ios (%s) POST: %s' % (request.user, request.POST))
    try:
        res = itunesiap.verify(request.POST.get("receipt", None), settings.ITUNES_PASSWORD, verify_ssl=False,
                               use_production=True, use_sandbox=True)
        log.info(res.receipt.last_in_app)
...

Any ideas what's wrong?

youknowone commented 6 years ago

Hello. Do you have any expected result? I want to know there was actually no in_apps in the receipt or itunesiap failed to bring the data even if they were in receipts.

maccinza commented 6 years ago

I'm also experiencing a similar IndexError in one of my api views:

# ...
if sandbox:
    with itunesiap.env.sandbox:
        try:
            response = itunesiap.verify(raw_data, use_sandbox=True)
            return True, response
        except (ItunesServerNotAvailable, ItunesServerNotReachable):
            no_response = True
        except itunesiap.exc.InvalidReceipt as exc:
            error = exc.description

Sentry actually indicates the error/exception being raised when exiting the contextmanager:

itunesiap/environment.py in __exit__ at line 42
        self._ctx_id = len(self._stack)
        self.push()
        return self
    def __exit__(self, exc_type, exc_value, tb):
        self._stack.pop(self._ctx_id)  # this line

Any clues on what might be happening?

evgenybf commented 5 years ago

I've come across the same issue. In my case it is caused because 'in_app' list can be empty.

    return response.receipt.last_in_app
  File "/home/vagrant/.local/share/virtualenvs/Server/lib/python3.6/site-packages/itunesiap/receipt.py", line 310, in last_in_app
    self.in_app, key=lambda x: x['original_purchase_date_ms'])[-1]
IndexError: list index out of range

https://stackoverflow.com/questions/36010595/itunes-reciept-validation-in-app-empty

An empty in_app array indicates that StoreKit has not recorded any transactions for that user yet. It may be that the application receipt has not yet been updated. When this happens, your app can inform the user that the receipt does not appear current and ask whether to refresh it. Upon user agreement, your app should use the SKReceiptRefreshRequest class to update the receipt. At this point, if StoreKit has recorded a purchase for the user, your app receipt will show it in in_app. See Refreshing the App Receipt for more information on how to update a receipt.

evgenybf commented 4 years ago

It occurred to me, that the algo used to retrieve last_in_app is wrong. There can be several transactions having the same original_purchase_date. For example, it's true for subscriptions which share the same original_transaction_id:

     {
            "quantity": "1",
            "product_id": "com.subscription",
            "transaction_id": "1000000581056268",
            "original_transaction_id": "1000000506536546",
            "purchase_date": "2019-02-28 11:16:27 Etc/GMT",
            "purchase_date_ms": "1551352587000",
            "purchase_date_pst": "2019-02-28 03:16:27 America/Los_Angeles",
            "original_purchase_date": "2019-02-28 11:11:27 Etc/GMT",
            "original_purchase_date_ms": "1551352287000",
            "original_purchase_date_pst": "2019-02-28 03:11:27 America/Los_Angeles",
            "expires_date": "2019-02-28 11:21:27 Etc/GMT",
            "expires_date_ms": "1551352887000",
            "expires_date_pst": "2019-02-28 03:21:27 America/Los_Angeles",
            "web_order_line_item_id": "1000000042979542",
            "is_trial_period": "false",
            "is_in_intro_offer_period": "false"
        },
        {
            "quantity": "1",
            "product_id": "com.subscription",
            "transaction_id": "1000000581056278",
            "original_transaction_id": "1000000506536546",
            "purchase_date": "2019-02-28 11:21:27 Etc/GMT",
            "purchase_date_ms": "1551352887000",
            "purchase_date_pst": "2019-02-28 03:21:27 America/Los_Angeles",
            "original_purchase_date": "2019-02-28 11:11:27 Etc/GMT",
            "original_purchase_date_ms": "1551352287000",
            "original_purchase_date_pst": "2019-02-28 03:11:27 America/Los_Angeles",
            "expires_date": "2019-02-28 11:26:27 Etc/GMT",
            "expires_date_ms": "1551353187000",
            "expires_date_pst": "2019-02-28 03:26:27 America/Los_Angeles",
            "web_order_line_item_id": "1000000042979655",
            "is_trial_period": "false",
            "is_in_intro_offer_period": "false"
        },

So, the correct way to sort the transactionы is to sort them by "purchase_date_ms" instead. Actually, the key may need to be even more complex:

    @staticmethod
    def _get_transaction_date_ms(in_app):
        try:
            return in_app.cancellation_date_ms
        except MissingFieldError:
            pass
        try:
            return in_app.purchase_date_ms
        except MissingFieldError:
            pass
        return in_app.original_purchase_date_ms
youknowone commented 4 years ago

@maccinza @aaksarin Because I don't work for in-app-purchase anymore, it is hard to check issues. Can anyone share a full receipt with this error please?

@evgenybf I tried to reproduce the issue with your example but it passed tests. Would you share full receipt please?

A patch with test cases are also very appreciated :)

datawaslost commented 4 years ago

I'm having the same issue - while it passes all sandbox tests, verifying some real-world subscription transactions returns an empty in_app array, which errors out when we try to access response.receipt.last_in_app.

I haven't been able to replicate the conditions under which we get the empty in_app.

It's an auto-renewing subscription, if that makes any difference - might be related to this: https://developer.apple.com/library/archive/technotes/tn2413/_index.html#//apple_ref/doc/uid/DTS40016228-CH1-RECEIPT-MY_APP_VALIDATES_ITS_RECEIPT_WITH_THE_APP_STORE_VIA_PAYMENTQUEUE_UPDATEDTRANSACTIONS__AFTER_A_SUCCESSFUL_PURCHASE__HOWEVER__THE_RETURNED_RECEIPT_CONTAINS_AN_EMPTY_IN_APP_ARRAY_RATHER_THAN_THE_EXPECTED_PRODUCTS_

datawaslost commented 4 years ago

While it'd be good to get at the deeper issue, a partial solution would be to simply return None for last_in_app when the in_app list is empty.