def test_shortcut(self): try: from testdata import sandbox_receipt except ImportError: print('No receipt data to test') return mode = itunesiap.get_verification_mode() itunesiap.set_verification_mode('sandbox') itunesiap.verify(sandbox_receipt) itunesiap.set_verification_mode(mode)
def test_invalid_receipt(): request = itunesiap.Request('wrong receipt') with pytest.raises(itunesiap.exc.InvalidReceipt): request.verify(env=itunesiap.env.production) with pytest.raises(itunesiap.exc.InvalidReceipt): request.verify(env=itunesiap.env.sandbox) try: itunesiap.verify('bad data') except itunesiap.exc.InvalidReceipt as e: print(e) # __str__ test print(repr(e)) # __repr__ test
def _verify_itunes_receipt(receipt_data): expires_date_ms = "0" code = 200 itunes_shared_secret = os.environ['SELSTA101_ITUNES_SHARED_SECRET'] try: with itunesiap.env.review: response = itunesiap.verify(receipt_data, itunes_shared_secret) in_apps = response.receipt.in_app for i in in_apps: new_expires_date_ms = i["expires_date_ms"] if int(new_expires_date_ms) > int(expires_date_ms): expires_date_ms = new_expires_date_ms except itunesiap.exc.InvalidReceipt: code = 400 logger.error("invalid receipt") except itunesiap.exc.ItunesServerNotAvailable: code = 444 logger.error("ItunesServiceNotAvailable") except itunesiap.exc.ItunesServerNotReachable: code = 408 logger.error("iTunesServerNotReachable") except Exception: code = 500 logger.error("Unexpected error, itunesiap") if code == 500: """This case is a bug in itunesiap module on rare. It will be working to try once again.""" return _verify_itunes_receipt(receipt_data) return code, expires_date_ms
async def verifyOrder(receipt: str, orderId: str, db: Session = Depends(get_db), device: int = Header(None), userId: int = Depends(token_is_true)): try: response = itunesiap.verify(str) db_order = crud.getOrderByOrderId(orderId=orderId) if db_order: # response.receipt.last_login_time pass else: return BaseErrResponse(code=403, message="订单不存在") except itunesiap.exc.InvalidReceipt as e: return BaseErrResponse(code=403, message="订单校验失败")
def validate(): # Setup parser p = optparse.OptionParser( description=' Validate iTunes in-app purchase receipt', prog='validate.py', version='validate 0.1', usage='%prog receipt.txt' ) options, arguments = p.parse_args() if len(arguments) == 1 and os.path.exists(arguments[0]): f = open(arguments[0]) try: # base64-encoded data from file argument response = itunesiap.verify(f.read()) print response.receipt.last_in_app.product_id except itunesiap.exc.InvalidReceipt as e: print 'invalid receipt: ' + e.description else: p.print_help()
def itunes_response_legacy1(raw_receipt_legacy): response = itunesiap.verify(raw_receipt_legacy, env=itunesiap.env.sandbox) return getattr(response, '_')
def test_shortcut(): """Test shortcuts""" with itunesiap.env.sandbox: itunesiap.verify(LEGACY_RAW_RECEIPT)
.. [#document] https://developer.apple.com/library/ios/#documentation/NetworkingInternet/Conceptual/StoreKitGuide/VerifyingStoreReceipts/VerifyingStoreReceipts.html#//apple_ref/doc/uid/TP40008267-CH104-SW1 """ import json import requests import pytz import datetime from mock import patch import pytest import itunesiap LEGACY_RAW_RECEIPT = '''ewoJInNpZ25hdHVyZSIgPSAiQW1vSjJDNFhra1hXcngwbDBwMUVCMkhqdndWRkJPN3NxaHRPYVpYWXNtd29PblU4dkNYNWZJWFV6SmpwWVpwVGJ1bTJhWW5kci9uOHlBc2czUXc0WUZHMUtCbEpLSjU2c1gzcEpmWTRZd2hEMmJsdm1lZVowZ0FXKzNiajBRWGVjUWJORTk5b2duK09janY2U3dFSEdpdkRIY0FRNzBiMTYxekdpbTk2WHVKTkFBQURWekNDQTFNd2dnSTdvQU1DQVFJQ0NHVVVrVTNaV0FTMU1BMEdDU3FHU0liM0RRRUJCUVVBTUg4eEN6QUpCZ05WQkFZVEFsVlRNUk13RVFZRFZRUUtEQXBCY0hCc1pTQkpibU11TVNZd0pBWURWUVFMREIxQmNIQnNaU0JEWlhKMGFXWnBZMkYwYVc5dUlFRjFkR2h2Y21sMGVURXpNREVHQTFVRUF3d3FRWEJ3YkdVZ2FWUjFibVZ6SUZOMGIzSmxJRU5sY25ScFptbGpZWFJwYjI0Z1FYVjBhRzl5YVhSNU1CNFhEVEE1TURZeE5USXlNRFUxTmxvWERURTBNRFl4TkRJeU1EVTFObG93WkRFak1DRUdBMVVFQXd3YVVIVnlZMmhoYzJWU1pXTmxhWEIwUTJWeWRHbG1hV05oZEdVeEd6QVpCZ05WQkFzTUVrRndjR3hsSUdsVWRXNWxjeUJUZEc5eVpURVRNQkVHQTFVRUNnd0tRWEJ3YkdVZ1NXNWpMakVMTUFrR0ExVUVCaE1DVlZNd2daOHdEUVlKS29aSWh2Y05BUUVCQlFBRGdZMEFNSUdKQW9HQkFNclJqRjJjdDRJclNkaVRDaGFJMGc4cHd2L2NtSHM4cC9Sd1YvcnQvOTFYS1ZoTmw0WElCaW1LalFRTmZnSHNEczZ5anUrK0RyS0pFN3VLc3BoTWRkS1lmRkU1ckdYc0FkQkVqQndSSXhleFRldngzSExFRkdBdDFtb0t4NTA5ZGh4dGlJZERnSnYyWWFWczQ5QjB1SnZOZHk2U01xTk5MSHNETHpEUzlvWkhBZ01CQUFHamNqQndNQXdHQTFVZEV3RUIvd1FDTUFBd0h3WURWUjBqQkJnd0ZvQVVOaDNvNHAyQzBnRVl0VEpyRHRkREM1RllRem93RGdZRFZSMFBBUUgvQkFRREFnZUFNQjBHQTFVZERnUVdCQlNwZzRQeUdVakZQaEpYQ0JUTXphTittVjhrOVRBUUJnb3Foa2lHOTJOa0JnVUJCQUlGQURBTkJna3Foa2lHOXcwQkFRVUZBQU9DQVFFQUVhU2JQanRtTjRDL0lCM1FFcEszMlJ4YWNDRFhkVlhBZVZSZVM1RmFaeGMrdDg4cFFQOTNCaUF4dmRXLzNlVFNNR1k1RmJlQVlMM2V0cVA1Z204d3JGb2pYMGlreVZSU3RRKy9BUTBLRWp0cUIwN2tMczlRVWU4Y3pSOFVHZmRNMUV1bVYvVWd2RGQ0TndOWXhMUU1nNFdUUWZna1FRVnk4R1had1ZIZ2JFL1VDNlk3MDUzcEdYQms1MU5QTTN3b3hoZDNnU1JMdlhqK2xvSHNTdGNURXFlOXBCRHBtRzUrc2s0dHcrR0szR01lRU41LytlMVFUOW5wL0tsMW5qK2FCdzdDMHhzeTBiRm5hQWQxY1NTNnhkb3J5L0NVdk02Z3RLc21uT09kcVRlc2JwMGJzOHNuNldxczBDOWRnY3hSSHVPTVoydG04bnBMVW03YXJnT1N6UT09IjsKCSJwdXJjaGFzZS1pbmZvIiA9ICJld29KSW05eWFXZHBibUZzTFhCMWNtTm9ZWE5sTFdSaGRHVXRjSE4wSWlBOUlDSXlNREV5TFRBNUxUSXdJREU0T2pNeE9qTTRJRUZ0WlhKcFkyRXZURzl6WDBGdVoyVnNaWE1pT3dvSkluVnVhWEYxWlMxcFpHVnVkR2xtYVdWeUlpQTlJQ0kwTW1NeFlqTmtORFUxTmpNNE1qQmtaRGxoTlRsak56bGhOelUyTkRFd01ERm1ZemcxWlRNNUlqc0tDU0p2Y21sbmFXNWhiQzEwY21GdWMyRmpkR2x2YmkxcFpDSWdQU0FpTVRBd01EQXdNREExTmpFMk1UYzJOQ0k3Q2draVluWnljeUlnUFNBaU1TNHdJanNLQ1NKMGNtRnVjMkZqZEdsdmJpMXBaQ0lnUFNBaU1UQXdNREF3TURBMU5qRTJNVGMyTkNJN0Nna2ljWFZoYm5ScGRIa2lJRDBnSWpFaU93b0pJbTl5YVdkcGJtRnNMWEIxY21Ob1lYTmxMV1JoZEdVdGJYTWlJRDBnSWpFek5EZ3hPVEV3T1RneE9USWlPd29KSW5CeWIyUjFZM1F0YVdRaUlEMGdJa0poZEhSc1pVZHZiR1ExTUNJN0Nna2lhWFJsYlMxcFpDSWdQU0FpTlRVME5EazVNekExSWpzS0NTSmlhV1FpSUQwZ0ltTnZiUzUyWVc1cGJHeGhZbkpsWlhwbExtbG5kVzVpWVhSMGJHVWlPd29KSW5CMWNtTm9ZWE5sTFdSaGRHVXRiWE1pSUQwZ0lqRXpORGd4T1RFd09UZ3hPVElpT3dvSkluQjFjbU5vWVhObExXUmhkR1VpSUQwZ0lqSXdNVEl0TURrdE1qRWdNREU2TXpFNk16Z2dSWFJqTDBkTlZDSTdDZ2tpY0hWeVkyaGhjMlV0WkdGMFpTMXdjM1FpSUQwZ0lqSXdNVEl0TURrdE1qQWdNVGc2TXpFNk16Z2dRVzFsY21sallTOU1iM05mUVc1blpXeGxjeUk3Q2draWIzSnBaMmx1WVd3dGNIVnlZMmhoYzJVdFpHRjBaU0lnUFNBaU1qQXhNaTB3T1MweU1TQXdNVG96TVRvek9DQkZkR012UjAxVUlqc0tmUT09IjsKCSJlbnZpcm9ubWVudCIgPSAiU2FuZGJveCI7CgkicG9kIiA9ICIxMDAiOwoJInNpZ25pbmctc3RhdHVzIiA9ICIwIjsKfQ==''' with itunesiap.env.sandbox: LEGACY_RESPONSE = itunesiap.verify(LEGACY_RAW_RECEIPT)._ LEGACY_RESPONSE2 = { u'status': 0, u'receipt': { u'purchase_date_pst': u'2013-01-01 00:00:00 America/Los_Angeles', u'product_id': u'TestProduction1', u'original_transaction_id': u'1000000012345678', u'unique_identifier': u'bcbdb3d45543920dd9sd5c79a72948001fc22a39', u'original_purchase_date_pst': u'2013-01-01 00:00:00 America/Los_Angeles', u'original_purchase_date': u'2013-01-01 00:00:00 Etc/GMT', u'bvrs': u'1.0', u'original_purchase_date_ms': u'1348200000000', u'purchase_date': u'2013-01-01 00:00:00 Etc/GMT', u'item_id': u'500000000', u'purchase_date_ms': u'134820000000', u'bid': u'org.youknowone.itunesiap',
def test_shortcut(): from testdata import sandbox_receipt verify(sandbox_receipt)
def process_itunes_receipt( self, user, itunes_receipt, log_purchase=True, ): ''' Subscribes if valid. If log_purchase is True, will log only if the receipt didn't already exist. Will raise InvalidReceipt error if invalid. Raises OutOfDateReceiptError if not the latest receipt on file. ''' try: response = itunesiap.verify( itunes_receipt, password=settings.ITUNES_SHARED_SECRET, env=itunesiap.env.review) latest_receipt_info = response.latest_receipt_info[-1] except Exception as e: InAppPurchaseLogItem.objects.get_or_create( subscriber=user, itunes_receipt=itunes_receipt, ) raise e from None original_transaction_id = ( latest_receipt_info['original_transaction_id']) is_trial_period = ( latest_receipt_info['is_trial_period'].lower() == 'true') purchase_date = _receipt_date_to_datetime( latest_receipt_info['purchase_date_ms']) # Confirm this incoming receipt isn't older than one on file. try: existing_subscription = Subscription.objects.get( original_transaction_id=original_transaction_id) existing_purchase_date = existing_subscription.purchase_date if ( existing_purchase_date is not None and purchase_date > existing_purchase_date ): raise OutOfDateReceiptError() except Subscription.DoesNotExist: pass if log_purchase: InAppPurchaseLogItem.objects.get_or_create( subscriber=user, itunes_receipt=itunes_receipt, original_transaction_id=original_transaction_id, ) self.model.objects.subscribe( user, original_transaction_id, _receipt_date_to_datetime(latest_receipt_info['expires_date_ms']), purchase_date, is_trial_period=is_trial_period, ) logger.info('Processed iTunes receipt')
def process_itunes_subscription_update_notification(self, notification): ''' Will raise InvalidReceipt error if invalid. ''' shared_secret = notification['password'] if shared_secret != settings.ITUNES_SHARED_SECRET: raise PermissionDenied('Invalid iTunes shared secret.') try: receipt_info = notification['latest_receipt_info'] except KeyError: receipt_info = notification['latest_expired_receipt_info'] notification_type = notification['notification_type'] SubscriptionUpdateNotificationLogItem.objects.create( production_environment=(notification['environment'] == 'PROD'), notification_type=notification_type, receipt_info=receipt_info, original_transaction_id= receipt_info['original_transaction_id']) if notification['environment'] == 'PROD': environment = itunesiap.env.production else: environment = itunesiap.env.sandbox if notification_type in [ 'RENEWAL', 'INTERACTIVE_RENEWAL', ]: receipt = notification['latest_receipt'] itunesiap.verify(receipt, password=settings.ITUNES_SHARED_SECRET) original_transaction_id = receipt_info['original_transaction_id'] subscription = Subscription.objects.get( original_transaction_id=original_transaction_id) subscription.active = True elif notification_type == 'CANCEL': receipt = itunes_receipt['latest_expired_receipt'] itunesiap.verify(receipt, password=settings.ITUNES_SHARED_SECRET) original_transaction_id = receipt_info['original_transaction_id'] subscription = Subscription.objects.get( original_transaction_id=original_transaction_id) subscription.active = False elif notification_type == 'DID_CHANGE_RENEWAL_PREF': # Customer changed the plan that takes affect at the next # subscription renewal. Current active plan is not affected. return elif notification_type == 'INITIAL_BUY': # Doesn't have an original_transaction_id yet so it's useless. # See https://forums.developer.apple.com/thread/98799 return elif notification_type == 'DID_CHANGE_RENEWAL_STATUS': # Indicates a change in the subscription renewal status. return subscription.sandbox = environment == itunesiap.env.sandbox subscription.latest_itunes_receipt = receipt subscription.expires_date = _receipt_date_to_datetime( receipt_info['expires_date']) subscription.is_trial_period = ( receipt_info['is_trial_period'].lower() == 'true') subscription.save() logger.info('Processed iTunes subscription update notification')
def test_shortcut(raw_receipt_legacy): """Test shortcuts""" itunesiap.verify(raw_receipt_legacy, env=itunesiap.env.sandbox)
def test_timeout(): with pytest.raises(itunesiap.exceptions.ItunesServerNotReachable): itunesiap.verify('DummyReceipt', timeout=0.0001)
def iap_verify_receipt(self, verify_from, appid, amount, app_order_id, good_name, pay_channel, userid, **kwargs): '''使用rawdata作为凭证请求苹果的服务器,获取支付回执信息''' args_string = '\t'.join([ verify_from, appid, amount, app_order_id, good_name, pay_channel, userid ]) # for logging raw_data = kwargs.get('raw_data') raw_data_digest = hashlib.sha1( raw_data).hexdigest() # 用于判断是否重复使用了一个支付凭证,如果重复直接拒绝 iap_receipt_history, created = IAPReceiptHistory2.objects.get_or_create( iap_digest=raw_data_digest) # 本处理逻辑中涉及到的重要事件 IAP_VERIFY_ERROR = settings.API_IMPORTANT_EVENTS.IAP_VERIFY_ERROR IAP_VERIFY_INFO = settings.API_IMPORTANT_EVENTS.IAP_VERIFY_INFO REUQEST_U8 = settings.API_IMPORTANT_EVENTS.REUQEST_U8 raw_args = { 'appid': appid, 'amount': amount, 'app_order_id': app_order_id, 'good_name': good_name, 'pay_channel': pay_channel, 'userid': userid, } context = {} context['args_map'] = raw_args context['pay_channel'] = pay_channel if (not created) and (iap_receipt_history.state == 1): context['reason'] = 'duplicated receipt' _track(IAP_VERIFY_ERROR, context) return if verify_from != '1': context['reason'] = 'verify_from should be 1' _track(IAP_VERIFY_ERROR, context) return try: IS_SANDBOX = True response = None try: with itunesiap.env.review: response = itunesiap.verify(raw_data) except itunesiap.exceptions.InvalidReceipt as e: context['reason'] = 'exception when request iap endpoint %s' % str( e) _track(IAP_VERIFY_ERROR, context) if not response: context['reason'] = 'response from iap is none' _track(IAP_VERIFY_ERROR, context) return if response.status == 0: IS_SANDBOX = response['environment'].lower() == 'sandbox' NEED_NOTIFY = True try: app = App.objects.get(appid=appid) except App.DoesNotExist: context['reason'] = 'app not exists %s' % appid _track(IAP_VERIFY_ERROR, context) return ###### 校验包名 try: response.receipt.in_app.sort( key=lambda x: int(getattr(x, 'original_purchase_date_ms'))) last_in_app = response.receipt.last_in_app ios_receipt_logger.info('{}\t{}\t{}\t{}'.format( raw_data_digest, raw_data, args_string, response)) # 记录支付凭证摘要何其原始值的对应关系到日志 # 通过original_transaction_id防止重复发货 try: _original_transaction_id = getattr( last_in_app, 'original_transaction_id') except: _original_transaction_id = None original_transaction_ids = IAPReceiptHistory2.objects.filter( original_transaction_id=_original_transaction_id) if original_transaction_ids.exists(): context['reason'] = 'duplicated original_transaction_id' context[ 'original_transaction_id'] = _original_transaction_id _track(IAP_VERIFY_ERROR, context) return # 通过original_transaction_id防止重复发货 END except IndexError: context['reason'] = 'no last_in_app found' _track(IAP_VERIFY_ERROR, context) return try: bundle_id = response.receipt['bundle_id'] except: bundle_id = None if not bundle_id: try: bundle_id = last_in_app.bid except AttributeError as _: bundle_id = None if not bundle_id: context['reason'] = 'bundle_id is empty' _track(IAP_VERIFY_ERROR, context) return else: package_names = app.package_names try: package_names_info = json.loads(package_names) except Exception as e: context['reason'] = 'bundle_id not configured properly' _track(IAP_VERIFY_ERROR, context) return if bundle_id not in package_names_info: # 首先,确保包名是合法的 context['reason'] = 'invalid bundle_id' _track(IAP_VERIFY_ERROR, context) return else: # 包名(bundle_id)确定是合法的,就要再进一步从product_id(bundleid-currency-goodid-realmoney)中获取到,此支付订单的真实的订单价格 SEP = '_' product_id = last_in_app.product_id try: _bundleid, _currency, _goodid, realmoney = product_id.split( SEP) except: context[ 'reason'] = 'cannot extract the 4 parts from product_id %s' % product_id _track(IAP_VERIFY_ERROR, context) return if bundle_id != _bundleid: context[ 'info'] = 'bundle_id from iap mismatch bundle_id extracted from product_id %s %s' % ( bundle_id, _bundleid) _track(IAP_VERIFY_INFO, context) try: realmoney = float(realmoney) # 单位为元 real_amount = int(realmoney * 100) # 元转换为分 except Exception as _: context[ 'reason'] = 'error when reading realmony from product_id' _track(IAP_VERIFY_ERROR, context) return ###### 校验包名 END ###### 校验包的审核状态,如果过审,则禁用沙箱支付 package_online = package_names_info[bundle_id]['production'] == '1' if not IS_SANDBOX: order_status = 'S' else: if not package_online: order_status = 'SS' else: NEED_NOTIFY = False # 如果已经上线,禁用沙箱支付 order_status = 'E' # 同时将订单状态设置为异常 ###### 校验包的审核状态,如果过审,则禁用沙箱支付 END try: user = User.objects.get(id=userid) except User.DoesNotExist: context['reason'] = 'user not exists' _track(IAP_VERIFY_ERROR, context) return if pay_channel != '99': # 苹果iTunes支付 context['reason'] = 'invalid pay_channel' _track(IAP_VERIFY_ERROR, context) return orders = UserGameOrder.objects.filter(game_order_id=app_order_id) if not orders.exists(): # 如果订单不存在,创建订单 platform = 2 # 手游 passthrough = kwargs.get('passthrough', '') game_callback_url = kwargs.get('game_callback_url', '') # 创建本地订单 order = UserGameOrder.create_order( user=user, real_amount=real_amount, currency=_currency, app=app, game_order_id=app_order_id, amount=amount, callback_url=game_callback_url, good_name=good_name, passthrough=passthrough, platform=platform, pay_channel=pay_channel) # 本地系统为本订单生成trade id order.trade_id = uuid.uuid4().get_hex() else: order = orders[0] order.order_status = order_status # 根据上下文信息修改订单状态 try: # 保存订单 order.save() # 防止应用内购买重复刷单状态更新 def _getattr(name): try: return getattr(last_in_app, name) except AttributeError: return None attr_names = [ 'quantity', 'product_id', 'transaction_id', 'purchase_date_ms', 'original_transaction_id', 'original_purchase_date_ms' ] attr_values = map(_getattr, attr_names) for item in zip(attr_names, attr_values): setattr(iap_receipt_history, item[0], item[1]) iap_receipt_history.bundle_id = bundle_id iap_receipt_history.trade_id = order.trade_id iap_receipt_history.is_sandbox = IS_SANDBOX iap_receipt_history.state = 0 # 当前处于未验证状态 iap_receipt_history.save() # 防止应用内购买重复刷单状态更新 END except Exception as save_exc: context['info'] = 'failed to create local order' _track(IAP_VERIFY_INFO, context) # 虽然本地订单保存不成功,但是还是要通知U8服务器,故此处不返回 if not NEED_NOTIFY: context[ 'reason'] = 'sandbox receipt trying to buy in production envronment' _track(IAP_VERIFY_ERROR, context) return request_args = get_callback_arg_tuples(order, others=[ ('ProductID', product_id) ]) # 获取回调参数,用于请求U8服务器 request_query_str = '&'.join( ['='.join(item) for item in request_args]) pay_callback_url = app.pay_callback_url context['pay_callback_url'] = pay_callback_url # 日志记录 parsed_u8_callback_url = urlparse.urlparse(pay_callback_url) new_u8_parsed_callback_url = urlparse.ParseResult( scheme=parsed_u8_callback_url.scheme, netloc=parsed_u8_callback_url.netloc, path=parsed_u8_callback_url.path, params=parsed_u8_callback_url.params, query=request_query_str, fragment=parsed_u8_callback_url.fragment) new_u8_callback_url = urlparse.urlunparse( new_u8_parsed_callback_url) callback_sign = get_signature(app.appsecret.encode('utf-8'), new_u8_callback_url) request_args.append(('Sign', callback_sign)) args_map = dict(request_args) request_obj = urllib2.Request(pay_callback_url) # 创建请求对象 request_obj.add_data(urllib.urlencode(args_map)) # 添加请求参数 response = urllib2.urlopen( request_obj, timeout=settings.PAY_CALLBACK_TIMEOUT).read() response_map = json.loads(response) context['response_map'] = response_map # 日志记录 if response_map['status'] == 'success': _track(REUQEST_U8, context) iap_receipt_history.state = 1 # 标记为验证成功 iap_receipt_history.save() set_user_ispay_cache(app.appid, user.id, real_amount) # 设置付费标记 event_name = settings.API_IMPORTANT_EVENTS.PAY_SUCCESS _track(event_name, context) else: iap_receipt_history.state = 2 # 标记为验证失败 iap_receipt_history.save() raise U8ResponseException( response_map['description']) # 向外层传播本异常,以引入重试机制 else: context['reason'] = 'iap endpoint return none zero code' _track(IAP_VERIFY_ERROR, context) except Exception as ee: # 有异常的情况下,重试机制介入 raise self.retry( exc=ee, max_retries=settings.CELERY_TASK_RETRY_POLICY_MAX_RETRIES, countdown=settings.CELERY_TASK_RETRY_POLICY[self.request.retries])
def test_shortcut(self): mode = itunesiap.get_verification_mode() itunesiap.set_verification_mode('sandbox') itunesiap.verify(sandbox_receipt) itunesiap.set_verification_mode(mode)