def _callback_url(request, is_success): status = is_success and 'success' or 'error' signed_notice = request.POST['signed_notice'] statsd.incr('purchase.payment_{0}_callback.received'.format(status)) # This is currently only used by Bango and Zippy. # Future providers should probably get added to the notification # abstraction in provider/views.py provider = ProviderHelper(settings.PAYMENT_PROVIDER) if provider.is_callback_token_valid(signed_notice): statsd.incr('purchase.payment_{0}_callback.ok'.format(status)) log.info('Callback {0} token was valid.'.format(status)) querystring = http.QueryDict(signed_notice) if 'ext_transaction_id' in querystring: ext_transaction_id = querystring['ext_transaction_id'] if is_success: tasks.payment_notify.delay(ext_transaction_id) else: tasks.chargeback_notify.delay(ext_transaction_id) return http.HttpResponse(status=204) else: statsd.incr('purchase.payment_{0}_callback.incomplete' ''.format(status)) log.error('Callback {0} token was incomplete: ' '{1}'.format(status, querystring)) else: statsd.incr('purchase.payment_{0}_callback.fail'.format(status)) log.error('Callback {0} token was invalid: ' '{1}'.format(status, signed_notice)) return http.HttpResponseBadRequest()
def notification(request, provider_name): """ Handle server to server notification responses. """ provider = ProviderHelper(provider_name) try: provider.server_notification(request) except msg.DevMessage as m: return HttpResponse(m.code, status=502) return HttpResponse('OK')
def notification(request, provider_name): """ Handle server to server notification responses. """ provider = ProviderHelper(provider_name) try: transaction_uuid = provider.server_notification(request) except msg.DevMessage as m: return HttpResponse(m.code, status=502) tasks.payment_notify.delay(transaction_uuid) return HttpResponse('OK')
def success(request, provider_name): provider = ProviderHelper(provider_name) if provider.name != 'reference': raise NotImplementedError( 'only the reference provider is implemented so far') try: transaction_id = provider.prepare_notice(request) except msg.DevMessage as m: return system_error(request, code=m.code) tasks.payment_notify.delay(transaction_id) return render(request, 'provider/success.html')
def _callback_url(request, is_success): status = is_success and "success" or "error" signed_notice = request.POST["signed_notice"] statsd.incr("purchase.payment_{0}_callback.received".format(status)) # This is currently only used by Bango and Zippy. # Future providers should probably get added to the notification # abstraction in provider/views.py provider = ProviderHelper.choose() if provider.is_callback_token_valid(signed_notice): statsd.incr("purchase.payment_{0}_callback.ok".format(status)) log.info("Callback {0} token was valid.".format(status)) querystring = http.QueryDict(signed_notice) if "ext_transaction_id" in querystring: ext_transaction_id = querystring["ext_transaction_id"] if is_success: tasks.payment_notify.delay(ext_transaction_id) else: tasks.chargeback_notify.delay(ext_transaction_id) return http.HttpResponse(status=204) else: statsd.incr("purchase.payment_{0}_callback.incomplete" "".format(status)) log.error("Callback {0} token was incomplete: " "{1}".format(status, querystring)) else: statsd.incr("purchase.payment_{0}_callback.fail".format(status)) log.error("Callback {0} token was invalid: " "{1}".format(status, signed_notice)) return http.HttpResponseBadRequest()
def test_from_wrong_mexican_operator(self): mcc = '334' # Mexico mnc = '03' # Movistar providers = ProviderHelper.supported_providers(mcc=mcc, mnc=mnc) provider_names = [provider.name for provider in providers] eq_(provider_names, [settings.PAYMENT_PROVIDER])
def test_not_from_mexico(self): mcc = '214' # Spain mnc = '01' # Vodaphone providers = ProviderHelper.supported_providers(mcc=mcc, mnc=mnc) provider_names = [provider.name for provider in providers] eq_(provider_names, [settings.PAYMENT_PROVIDER])
def test_from_boku_operator(self): mcc = '334' # Mexico mnc = '020' # AMX providers = ProviderHelper.supported_providers(mcc=mcc, mnc=mnc) provider_names = [provider.name for provider in providers] eq_(provider_names, [ BokuProvider.name, settings.PAYMENT_PROVIDER])
def notification(request, provider_name): """ Handle server to server notification responses. """ provider = ProviderHelper(provider_name) try: transaction_uuid = provider.server_notification(request) except msg.DevMessage as m: return HttpResponse(m.code, status=502) trans = client.get_transaction(transaction_uuid) log.info("Processing notification for transaction {t}; status={s}".format(t=transaction_uuid, s=trans["status"])) if trans["status"] == STATUS_COMPLETED: tasks.payment_notify.delay(transaction_uuid) return HttpResponse("OK")
def configure_transaction(request, trans=None): """ Begins a background task to configure a payment transaction. """ if request.session.get('is_simulation', False): log.info('is_simulation: skipping configure payments step') return False if not trans and not 'trans_id' in request.session: log.error('trans_id: not found in session') return False try: if not trans: trans = client.get_transaction(uuid=request.session['trans_id']) log.info('attempt to reconfigure trans {0} (status={1})' .format(request.session['trans_id'], trans['status'])) except ObjectDoesNotExist: trans = {} if trans.get('status') in constants.STATUS_RETRY_OK: new_trans_id = trans_id() log.info('retrying trans {0} (status={1}) as {2}' .format(request.session['trans_id'], trans['status'], new_trans_id)) request.session['trans_id'] = new_trans_id last_configured = request.session.get('configured_trans') if last_configured == request.session['trans_id']: log.info('trans %s (status=%r) already configured: ' 'skipping configure payments step' % (request.session['trans_id'], trans.get('status'))) return False # Prevent configuration from running twice. request.session['configured_trans'] = request.session['trans_id'] # Localize the product before sending it off to solitude/bango. _localize_pay_request(request) log.info('configuring payment in background for trans {t} (status={s}); ' 'Last configured: {c}'.format(t=request.session['trans_id'], s=trans.get('status'), c=last_configured)) network = request.session['notes'].get('network', {}) provider = ProviderHelper.choose(mcc=network.get('mcc'), mnc=network.get('mnc')) start_pay.delay(request.session['trans_id'], request.session['notes'], request.session['uuid'], provider.name) # We passed notes to start_pay (which saves it to the transaction # object), so delete it from the session to save cookie space. del request.session['notes'] return True
def error(request, provider_name): provider = ProviderHelper(provider_name) if provider.name != 'reference': raise NotImplementedError( 'only the reference provider is implemented so far') try: provider.prepare_notice(request) except msg.DevMessage as m: return system_error(request, code=m.code) # TODO: handle user cancellation, bug 957774. log.error('Fatal payment error for {provider}: {code}; query string: {qs}'. format(provider=provider.name, code=request.GET.get('ResponseCode'), qs=request.GET)) return system_error(request, code=msg.EXT_ERROR)
def success(request, provider_name): provider = ProviderHelper(provider_name) if provider.name != 'reference': raise NotImplementedError( 'only the reference provider is implemented so far') try: transaction_id = provider.prepare_notice(request) except msg.DevMessage as m: return system_error(request, code=m.code) tasks.payment_notify.delay(transaction_id) state, fxa_url = fxa_auth_info(request) ctx = {'start_view': 'payment-success', 'fxa_state': state, 'fxa_auth_url': fxa_url} return render(request, 'spa/index.html', ctx)
def error(request, provider_name): provider = ProviderHelper(provider_name) if provider.name != 'reference': raise NotImplementedError( 'only the reference provider is implemented so far') try: provider.prepare_notice(request) except msg.DevMessage as m: return system_error(request, code=m.code) # TODO: handle user cancellation, bug 957774. log.error('Fatal payment error for {provider}: {code}; query string: {qs}' .format(provider=provider.name, code=request.GET.get('ResponseCode'), qs=request.GET)) return system_error(request, code=msg.EXT_ERROR)
def success(request, provider_name): provider = ProviderHelper(provider_name) if provider.name != "reference": raise NotImplementedError("only the reference provider is implemented so far") try: transaction_id = provider.prepare_notice(request) except msg.DevMessage as m: return system_error(request, code=m.code) tasks.payment_notify.delay(transaction_id) if settings.SPA_ENABLE: state, fxa_url = fxa_auth_info(request) ctx = {"start_view": "payment-success", "fxa_state": state, "fxa_auth_url": fxa_url} return render(request, "spa/index.html", ctx) return render(request, "provider/success.html")
def success(request, provider_name): provider = ProviderHelper(provider_name) if provider.name != 'reference': raise NotImplementedError( 'only the reference provider is implemented so far') try: transaction_id = provider.prepare_notice(request) except msg.DevMessage as m: return system_error(request, code=m.code) tasks.payment_notify.delay(transaction_id) if settings.SPA_ENABLE: ctx = {'start_view': 'payment-success'} if settings.USE_FXA: ctx['fxa_state'], ctx['fxa_auth_url'] = fxa_auth_info(request) return render(request, 'spa/index.html', ctx) return render(request, 'provider/success.html')
def configure_transaction(request, trans=None): """ Begins a background task to configure a payment transaction. """ if request.session.get("is_simulation", False): log.info("is_simulation: skipping configure payments step") return False if not trans and not "trans_id" in request.session: log.error("trans_id: not found in session") return False try: if not trans: trans = client.get_transaction(uuid=request.session["trans_id"]) log.info("attempt to reconfigure trans {0} (status={1})".format(request.session["trans_id"], trans["status"])) except ObjectDoesNotExist: trans = {} if trans.get("status") in constants.STATUS_RETRY_OK: new_trans_id = trans_id() log.info( "retrying trans {0} (status={1}) as {2}".format(request.session["trans_id"], trans["status"], new_trans_id) ) request.session["trans_id"] = new_trans_id last_configured = request.session.get("configured_trans") if last_configured == request.session["trans_id"]: log.info( "trans %s (status=%r) already configured: " "skipping configure payments step" % (request.session["trans_id"], trans.get("status")) ) return False # Prevent configuration from running twice. request.session["configured_trans"] = request.session["trans_id"] # Localize the product before sending it off to solitude/bango. _localize_pay_request(request) log.info( "configuring payment in background for trans {t} (status={s}); " "Last configured: {c}".format(t=request.session["trans_id"], s=trans.get("status"), c=last_configured) ) network = request.session["notes"].get("network", {}) providers = ProviderHelper.supported_providers(mcc=network.get("mcc"), mnc=network.get("mnc")) start_pay.delay(request.session["trans_id"], request.session["notes"], request.session["uuid"], providers) # We passed notes to start_pay (which saves it to the transaction # object), so delete it from the session to save cookie space. del request.session["notes"] return True
def wait_to_finish(request, provider_name): """ After the payment provider finishes the pay flow, wait for completion. The provider redirects here so the UI can poll Solitude until the transaction is complete. """ helper = ProviderHelper(provider_name) trans_uuid = helper.provider.transaction_from_notice(request.GET) if not trans_uuid: # This could happen if someone is tampering with the URL or if # the payment provider changed their URL parameters. log.info('no transaction found for provider {p}; url: {u}' .format(p=helper.provider.name, u=request.get_full_path())) return HttpResponseNotFound() trans_url = reverse('provider.transaction_status', args=[trans_uuid]) return render(request, 'provider/wait-to-finish.html', {'transaction_status_url': trans_url})
def get_best_provider(price_point, seller_uuids, provider_names): """ Looks through the providers requested by user. Check the provider exists on the seller and then check the price point exists in the marketplace. """ log.info('Choosing best provider, requested: {p}'.format(p=provider_names)) for provider in provider_names: provider_seller_uuid = seller_uuids.get(provider) log.info('Provider: {p} {s} in sellers account'.format( p=provider, s='found' if provider_seller_uuid else 'NOT FOUND')) if provider_seller_uuid: prices = mkt_client.get_price(price_point, provider=provider) if not prices['prices']: log.info('No prices for provider: {p}'.format(p=provider)) continue log.info('Price found for provider: {p}'.format(p=provider)) return ProviderHelper(provider), provider_seller_uuid, prices raise NoValidSeller( 'Unable to find a valid seller_uuid ' 'using providers: {providers}'.format(providers=provider_names))
def test_from_wrong_mexican_operator(self): mcc = '334' # Mexico mnc = '03' # Movistar provider = ProviderHelper.choose(mcc=mcc, mnc=mnc) eq_(provider.name, BangoProvider.name)
def test_from_boku_operator(self): mcc = '334' # Mexico mnc = '020' # AMX provider = ProviderHelper.choose(mcc=mcc, mnc=mnc) eq_(provider.name, BokuProvider.name)
def test_supported_providers_returns_default_provider(self): providers = ProviderHelper.supported_providers() eq_(len(providers), 1) provider = providers[0] eq_(provider.name, settings.PAYMENT_PROVIDER)
class TestBango(TestCase): uuid = 'some:pin' seller = {'bango': {'seller': 's', 'resource_uri': 'r', 'package_id': '1234'}, 'resource_uri': '/seller/1', 'resource_pk': 'seller_pk'} def setUp(self): super(TestBango, self).setUp() self.slumber = mock.MagicMock() self.provider = ProviderHelper('bango', slumber=self.slumber) def start(self): return self.provider.start_transaction(*range(0, 9)) def test_create_without_bango_seller(self): self.slumber.generic.seller.get_object.return_value = { 'bango': None, 'resource_pk': '1', 'resource_uri': '/seller/1' } with self.assertRaises(ValueError): self.provider.create_product( external_id='ext:id', product_name='product name', generic_seller={}, provider_seller_uuid='provider_seller_uuid', generic_product={'resource_pk': '2', 'resource_uri': '/foo'}) self.slumber.generic.seller.get_object.assert_called_with( uuid='provider_seller_uuid') def test_create_bango_product(self): slumber = self.slumber slumber.bango.generic.post.return_value = {'product': 'some:uri'} slumber.bango.product.post.return_value = {'resource_uri': 'some:uri', 'bango_id': '5678'} assert self.provider.create_product( external_id='ext:id', product_name='product:name', generic_seller=self.seller, provider_seller_uuid='xyz') assert slumber.generic.product.post.called kw = slumber.generic.product.post.call_args[0][0] eq_(kw['external_id'], 'ext:id') eq_(slumber.bango.rating.post.call_count, 2) assert slumber.bango.premium.post.called def test_no_seller(self): slumber = self.slumber slumber.generic.seller.get_object_or_404.side_effect = ( ObjectDoesNotExist) with self.assertRaises(SellerNotConfigured): self.start() def test_no_bango_product(self): slumber = self.slumber slumber.generic.seller.get_object_or_404.return_value = self.seller slumber.bango.billing.post.return_value = { 'billingConfigurationId': 'bill_id'} slumber.bango.product.get_object_or_404.side_effect = ( ObjectDoesNotExist) trans_id, pay_url, seller_uuid = self.start() eq_(trans_id, 'bill_id') def test_with_bango_product(self): slumber = self.slumber slumber.generic.seller.get_object_or_404.return_value = self.seller slumber.bango.billing.post.return_value = { 'billingConfigurationId': 'bill_id'} slumber.bango.product.get_object.return_value = { 'resource_uri': 'foo'} trans_id, pay_url, seller_uuid = self.start() eq_(trans_id, 'bill_id') def test_pay_url(self): bill_id = '123' slumber = self.slumber slumber.generic.seller.get_object_or_404.return_value = self.seller slumber.bango.billing.post.return_value = { 'billingConfigurationId': bill_id} with self.settings( PAY_URLS={'bango': {'base': 'http://bango', 'pay': '/pay?bcid={uid_pay}'}}): trans_id, pay_url, seller_uuid = self.start() eq_(pay_url, 'http://bango/pay?bcid={b}'.format(b=bill_id))
class TestReferenceProvider(ProviderTestCase): def setUp(self): super(TestReferenceProvider, self).setUp() self.provider = ProviderHelper('reference', slumber=self.slumber) def test_start_with_existing_prod(self): self.slumber.provider.reference.transactions.post.return_value = { 'token': 'zippy-trans-token', } self.slumber.generic.buyer.get_object_or_404.return_value = { 'resource_uri': self.buyer_uri, } trans_id, pay_url, seller_id = self.configure( seller_uuid=self.seller_uuid, product_uuid=self.product_uuid) eq_(trans_id, 'zippy-trans-token') eq_(seller_id, self.seller_id) assert pay_url.endswith('tx={t}'.format(t=trans_id)), ( 'Unexpected: {url}'.format(url=pay_url)) kw = (self.slumber.provider.reference.products .get_object_or_404).call_args[1] eq_(kw['seller_product__seller'], self.seller_id) eq_(kw['seller_product__external_id'], 'app-xyz') self.slumber.generic.transaction.post.assert_called_with({ 'amount': '0.99', 'carrier': 'USA_TMOBILE', 'currency': 'EUR', 'provider': constants.PROVIDER_REFERENCE, 'region': '123', 'buyer': self.buyer_uri, 'seller': self.seller_uri, 'seller_product': self.product_uri, 'source': 'unknown', 'status': constants.STATUS_PENDING, 'type': constants.TYPE_PAYMENT, 'uuid': 'trans-xyz', }) def test_with_new_prod(self): name = 'Magic Unicorn' new_product_uuid = 'new-product' self.slumber.generic.buyer.get_object_or_404.return_value = { 'resource_uri': self.buyer_uri, } (self.slumber.generic.product.get_object_or_404 .side_effect) = ObjectDoesNotExist self.slumber.generic.product.post.return_value = { 'access': 1, 'public_id': '6597288d-7bce-409b-a35b-772acfe04b1e', 'external_id': self.product_uuid, 'seller': self.seller_uri, 'resource_uri': self.product_uri, } self.slumber.provider.reference.transactions.post.return_value = { 'token': 'zippy-trans-token', } seller_ref_uri = '/reference/seller/1' (self.slumber.provider.reference.sellers.get_object_or_404 .return_value) = { 'resource_uri': seller_ref_uri } seller_prod_uri = '/reference/product/1' self.slumber.provider.reference.products.post.return_value = { 'reference': { 'uuid': new_product_uuid }, 'resource_uri': seller_prod_uri } (self.slumber.provider.reference.products .side_effect) = ObjectDoesNotExist result = self.configure(seller_uuid=self.seller_uuid, product_uuid=self.product_uuid, product_name=name) eq_(result[0], 'zippy-trans-token') kw = self.slumber.provider.reference.products.post.call_args[0][0] eq_(kw['name'], name) eq_(kw['seller_reference'], seller_ref_uri) eq_(kw['seller_product'], self.product_uri) assert 'uuid' in kw, kw kw = self.slumber.provider.reference.transactions.post.call_args[0][0] eq_(kw['product_id'], new_product_uuid) eq_(kw['product_image_url'], '/todo/icons') assert kw['success_url'].endswith('/provider/reference/success'), ( 'Unexpected: {0}'.format(kw['success_url'])) assert kw['error_url'].endswith('/provider/reference/error'), ( 'Unexpected: {0}'.format(kw['error_url'])) self.slumber.generic.transaction.post.assert_called_with({ 'amount': '0.99', 'carrier': 'USA_TMOBILE', 'currency': 'EUR', 'provider': constants.PROVIDER_REFERENCE, 'region': '123', 'buyer': self.buyer_uri, 'seller': self.seller_uri, 'seller_product': self.product_uri, 'source': 'unknown', 'status': constants.STATUS_PENDING, 'type': constants.TYPE_PAYMENT, 'uuid': 'trans-xyz', }) def test_callback_validation_success(self): self.slumber.provider.reference.notices.post.return_value = { 'result': 'OK', } self.slumber.provider.reference.transactions.post.return_value = { 'token': 'zippy-trans-token', } self.configure(seller_uuid='seller-xyz', product_uuid='app-xyz') is_valid = self.provider.is_callback_token_valid({'foo': 'bar'}) eq_(is_valid, True) eq_(self.slumber.provider.reference.notices.post.call_args[0][0], {'qs': {'foo': 'bar'}}) def test_callback_validation_failure(self): self.slumber.provider.reference.notices.post.return_value = { 'result': 'FAIL', 'reason': 'signature mismatch', } self.slumber.provider.reference.transactions.post.return_value = { 'token': 'zippy-trans-token', } self.configure(seller_uuid='seller-xyz', product_uuid='app-xyz') is_valid = self.provider.is_callback_token_valid({'foo': 'bar'}) eq_(is_valid, False)
def test_not_from_mexico(self): mcc = '214' # Spain mnc = '01' # Vodaphone provider = ProviderHelper.choose(mcc=mcc, mnc=mnc) eq_(provider.name, BangoProvider.name)
def setUp(self): super(TestBoku, self).setUp() self.provider = ProviderHelper('boku', slumber=self.slumber)
def start_pay(transaction_uuid, notes, user_uuid, provider_name, **kw): """ Work with Solitude to begin a payment. This puts the transaction in a state where it's ready to be fulfilled by the payment provider. Arguments: **transaction_uuid** Unique identifier for a new transaction. **notes** Dict of notes about this transaction. **user_uuid** Unique identifier for the buyer user. **provider_name** One of a predefined strings to activate a payment provider. Example: 'bango' or 'reference' """ key = notes['issuer_key'] pay = notes['pay_request'] network = notes.get('network', {}) product_data = urlparse.parse_qs(pay['request'].get('productData', '')) provider = ProviderHelper(provider_name) try: seller_uuid = get_seller_uuid(key, product_data) try: application_size = int(product_data['application_size'][0]) except (KeyError, ValueError): application_size = None # Ask the marketplace for a valid price point. prices = mkt_client.get_price(pay['request']['pricePoint']) log.debug('pricePoint=%s prices=%s' % (pay['request']['pricePoint'], prices['prices'])) try: icon_url = (get_icon_url(pay['request']) if settings.USE_PRODUCT_ICONS else None) except: log.exception('Calling get_icon_url') icon_url = None log.info('icon URL for %s: %s' % (transaction_uuid, icon_url)) bill_id, pay_url, seller_id = provider.start_transaction( transaction_uuid, seller_uuid, pay['request']['id'], pay['request']['name'], # app/product name prices['prices'], icon_url, user_uuid, application_size, source='marketplace' if is_marketplace(key) else 'other', mcc=network.get('mcc'), mnc=network.get('mnc') ) trans_pk = client.slumber.generic.transaction.get_object( uuid=transaction_uuid)['resource_pk'] client.slumber.generic.transaction(trans_pk).patch({ 'notes': json.dumps(notes), 'uid_pay': bill_id, 'pay_url': pay_url, 'status': constants.STATUS_PENDING }) except Exception, exc: log.exception('while configuring for payment') etype, val, tb = sys.exc_info() raise exc, None, tb
def test_no_network(self): provider = ProviderHelper.choose() eq_(provider.name, BangoProvider.name)
class TestReferenceProvider(ProviderTestCase): def setUp(self): self.slumber = mock.MagicMock() self.provider = ProviderHelper('reference', slumber=self.slumber) def set_mocks(self, returns={}, keys=None, **kw): if not keys: keys = ('generic.seller', 'generic.product', 'generic.buyer', 'provider.reference.products', 'provider.reference.transactions',) return super(TestReferenceProvider, self).set_mocks( returns=returns, keys=keys, **kw) def test_start_with_existing_prod(self): seller_uuid = 'seller-xyz' product_uuid = 'app-xyz' self.set_mocks({ 'provider.reference.transactions': { 'method': 'post', 'return': { 'token': 'zippy-trans-token', } }}, seller_uuid=seller_uuid, product_uuid=product_uuid ) trans_id, pay_url, seller_uuid = self.configure( seller_uuid=seller_uuid, product_uuid=product_uuid) eq_(trans_id, 'zippy-trans-token') eq_(seller_uuid, seller_uuid) assert pay_url.endswith('tx={t}'.format(t=trans_id)), ( 'Unexpected: {url}'.format(url=pay_url)) kw = self.slumber.provider.reference.products\ .get_object_or_404.call_args[1] eq_(kw['external_id'], product_uuid) eq_(kw['seller_id'], seller_uuid) def test_with_new_prod(self): new_product_id = 66 product_uuid = 'app-xyz' seller_uuid = 'seller-xyz' self.set_mocks({ 'generic.product': { 'side_effect': ObjectDoesNotExist, }, 'provider.reference.transactions': { 'method': 'post', 'return': { 'token': 'zippy-trans-token', } }, 'provider.reference.products': { 'side_effect': ObjectDoesNotExist, }, 'provider.reference.sellers': { 'return': { 'resource_pk': seller_uuid, } }}, seller_uuid=seller_uuid ) self.slumber.provider.reference.products.post.return_value = { 'resource_pk': new_product_id, } result = self.configure(seller_uuid=seller_uuid, product_uuid=product_uuid) eq_(result[0], 'zippy-trans-token') kw = self.slumber.provider.reference.products.post.call_args[0][0] eq_(kw['external_id'], product_uuid) eq_(kw['seller_id'], seller_uuid) kw = self.slumber.provider.reference.transactions.post.call_args[0][0] eq_(kw['product_id'], new_product_id) eq_(kw['product_image_url'], '/todo/icons') assert kw['success_url'].endswith('/provider/reference/success'), ( 'Unexpected: {0}'.format(kw['success_url'])) assert kw['error_url'].endswith('/provider/reference/error'), ( 'Unexpected: {0}'.format(kw['error_url'])) def test_callback_validation_success(self): self.set_mocks({ 'provider.reference.notices': { 'method': 'post', 'return': { 'result': 'OK', } }, 'provider.reference.transactions': { 'method': 'post', 'return': { 'token': 'zippy-trans-token', } }}, product_uuid='XYZ' ) self.configure(seller_uuid='seller-xyz', product_uuid='app-xyz') is_valid = self.provider.is_callback_token_valid({'foo': 'bar'}) eq_(is_valid, True) eq_(self.slumber.provider.reference.notices.post.call_args[0][0], {'qs': {'foo': 'bar'}}) def test_callback_validation_failure(self): self.set_mocks({ 'provider.reference.notices': { 'method': 'post', 'return': { 'result': 'FAIL', 'reason': 'signature mismatch', } }, 'provider.reference.transactions': { 'method': 'post', 'return': { 'token': 'zippy-trans-token', } }}, product_uuid='XYZ' ) self.configure(seller_uuid='seller-xyz', product_uuid='app-xyz') is_valid = self.provider.is_callback_token_valid({'foo': 'bar'}) eq_(is_valid, False)
def setUp(self): super(TestReferenceProvider, self).setUp() self.provider = ProviderHelper('reference', slumber=self.slumber)
def configure_transaction(request, trans=None, mcc=None, mnc=None): """ Begins a background task to configure a payment transaction. Returns a tuple of (was_configured, error_code) * was_configured is a boolean * error_code, when not None, is an error code for the failure """ if request.session.get('is_simulation', False): log.info('is_simulation: skipping configure payments step') return (False, None) notes = request.session.get('notes', {}) if mcc and mnc: notes['network'] = {'mnc': mnc, 'mcc': mcc} else: # Reset network state to avoid leakage from previous states. notes['network'] = {} request.session['notes'] = notes log.info('Added mcc/mnc to session: ' '{network}'.format(network=notes['network'])) log.info('configuring transaction {0} from client' .format(request.session.get('trans_id'))) if not trans and 'trans_id' not in request.session: log.error('trans_id: not found in session') return (False, dev_messages.TRANS_MISSING) try: if not trans: trans = client.get_transaction(uuid=request.session['trans_id']) log.info('attempt to reconfigure trans {0} (status={1})' .format(request.session['trans_id'], trans['status'])) except ObjectDoesNotExist: trans = {} if trans.get('status') in constants.STATUS_RETRY_OK: new_trans_id = trans_id() log.info('retrying trans {0} (status={1}) as {2}' .format(request.session['trans_id'], trans['status'], new_trans_id)) request.session['trans_id'] = new_trans_id last_configured = request.session.get('configured_trans') if last_configured == request.session['trans_id']: log.info('trans %s (status=%r) already configured: ' 'skipping configure payments step' % (request.session['trans_id'], trans.get('status'))) return (False, None) # Localize the product before sending it off to solitude/bango. _localize_pay_request(request) log.info('configuring payment in background for trans {t} (status={s}); ' 'Last configured: {c}'.format(t=request.session['trans_id'], s=trans.get('status'), c=last_configured)) network = request.session['notes'].get('network', {}) providers = ProviderHelper.supported_providers( mcc=network.get('mcc'), mnc=network.get('mnc'), ) start_pay.delay(request.session['trans_id'], request.session['notes'], request.session['uuid'], [p.name for p in providers]) # Now that the background task has been started successfully, # prevent configuration from running twice. request.session['configured_trans'] = request.session['trans_id'] return (True, None)
class TestReferenceProvider(ProviderTestCase): def setUp(self): super(TestReferenceProvider, self).setUp() self.provider = ProviderHelper('reference', slumber=self.slumber) def test_start_with_existing_prod(self): self.slumber.provider.reference.transactions.post.return_value = { 'token': 'zippy-trans-token', } self.slumber.generic.buyer.get_object_or_404.return_value = { 'resource_uri': self.buyer_uri, } trans_id, pay_url, seller_id = self.configure( seller_uuid=self.seller_uuid, product_uuid=self.product_uuid) eq_(trans_id, 'zippy-trans-token') eq_(seller_id, self.seller_id) assert pay_url.endswith('tx={t}'.format(t=trans_id)), ( 'Unexpected: {url}'.format(url=pay_url)) kw = (self.slumber.provider.reference.products .get_object_or_404).call_args[1] eq_(kw['seller_product__seller'], self.seller_id) eq_(kw['seller_product__external_id'], 'app-xyz') self.slumber.generic.transaction.post.assert_called_with({ 'amount': '1.99', 'carrier': 'FAKE', 'currency': 'USD', 'provider': constants.PROVIDER_REFERENCE, 'region': 'CAN', 'buyer': self.buyer_uri, 'seller': self.seller_uri, 'seller_product': self.product_uri, 'source': 'unknown', 'status': constants.STATUS_PENDING, 'type': constants.TYPE_PAYMENT, 'uuid': 'trans-xyz', }) def test_with_new_prod(self): name = u'Ivan Krsti\u0107' new_product_uuid = 'new-product' self.slumber.generic.buyer.get_object_or_404.return_value = { 'resource_uri': self.buyer_uri, } (self.slumber.generic.product.get_object_or_404 .side_effect) = ObjectDoesNotExist self.slumber.generic.product.post.return_value = { 'access': 1, 'public_id': '6597288d-7bce-409b-a35b-772acfe04b1e', 'external_id': self.product_uuid, 'seller': self.seller_uri, 'resource_uri': self.product_uri, } self.slumber.provider.reference.transactions.post.return_value = { 'token': 'zippy-trans-token', } seller_ref_uri = '/reference/seller/1' (self.slumber.provider.reference.sellers.get_object_or_404 .return_value) = { 'resource_uri': seller_ref_uri } seller_prod_uri = '/reference/product/1' self.slumber.provider.reference.products.post.return_value = { 'reference': { 'uuid': new_product_uuid }, 'resource_uri': seller_prod_uri } (self.slumber.provider.reference.products .side_effect) = ObjectDoesNotExist result = self.configure(seller_uuid=self.seller_uuid, product_uuid=self.product_uuid, product_name=name) eq_(result[0], 'zippy-trans-token') kw = self.slumber.provider.reference.products.post.call_args[0][0] eq_(kw['name'], name) eq_(kw['seller_reference'], seller_ref_uri) eq_(kw['seller_product'], self.product_uri) assert 'uuid' in kw, kw kw = self.slumber.provider.reference.transactions.post.call_args[0][0] eq_(kw['product_id'], new_product_uuid) eq_(kw['product_image_url'], '/todo/icons') assert kw['success_url'].endswith('/provider/reference/success'), ( 'Unexpected: {0}'.format(kw['success_url'])) assert kw['error_url'].endswith('/provider/reference/error'), ( 'Unexpected: {0}'.format(kw['error_url'])) self.slumber.generic.transaction.post.assert_called_with({ 'amount': '1.99', 'carrier': 'FAKE', 'currency': 'USD', 'provider': constants.PROVIDER_REFERENCE, 'region': 'CAN', 'buyer': self.buyer_uri, 'seller': self.seller_uri, 'seller_product': self.product_uri, 'source': 'unknown', 'status': constants.STATUS_PENDING, 'type': constants.TYPE_PAYMENT, 'uuid': 'trans-xyz', }) def test_callback_validation_success(self): self.slumber.provider.reference.notices.post.return_value = { 'result': 'OK', } self.slumber.provider.reference.transactions.post.return_value = { 'token': 'zippy-trans-token', } self.configure(seller_uuid='seller-xyz', product_uuid='app-xyz') is_valid = self.provider.is_callback_token_valid({'foo': 'bar'}) eq_(is_valid, True) eq_(self.slumber.provider.reference.notices.post.call_args[0][0], {'qs': {'foo': 'bar'}}) def test_callback_validation_failure(self): self.slumber.provider.reference.notices.post.return_value = { 'result': 'FAIL', 'reason': 'signature mismatch', } self.slumber.provider.reference.transactions.post.return_value = { 'token': 'zippy-trans-token', } self.configure(seller_uuid='seller-xyz', product_uuid='app-xyz') is_valid = self.provider.is_callback_token_valid({'foo': 'bar'}) eq_(is_valid, False) @raises(ValueError) def test_no_usd(self): self.configure(prices={})
def setUp(self): super(TestBango, self).setUp() self.slumber = mock.MagicMock() self.provider = ProviderHelper('bango', slumber=self.slumber)
class TestBango(TestCase): uuid = 'some:pin' seller = { 'bango': { 'seller': 's', 'resource_uri': 'r', 'package_id': '1234' }, 'resource_uri': '/seller/1', 'resource_pk': 'seller_pk' } def setUp(self): super(TestBango, self).setUp() self.slumber = mock.MagicMock() self.provider = ProviderHelper('bango', slumber=self.slumber) def test_create_without_bango_seller(self): with self.assertRaises(ValueError): self.provider.create_product('ext:id', 'product name', { 'bango': None, 'resource_pk': '1', 'resource_uri': '/seller/1' }, generic_product={'resource_pk': '2'}) def test_create_bango_product(self): slumber = self.slumber slumber.bango.generic.post.return_value = {'product': 'some:uri'} slumber.bango.product.post.return_value = { 'resource_uri': 'some:uri', 'bango_id': '5678' } assert self.provider.create_product('ext:id', 'product:name', self.seller) assert slumber.generic.product.post.called kw = slumber.generic.product.post.call_args[0][0] eq_(kw['external_id'], 'ext:id') eq_(slumber.bango.rating.post.call_count, 2) assert slumber.bango.premium.post.called def test_no_seller(self): slumber = self.slumber slumber.generic.seller.get_object_or_404.side_effect = ( ObjectDoesNotExist) with self.assertRaises(SellerNotConfigured): self.provider.start_transaction(*range(0, 8)) def test_no_bango_product(self): slumber = self.slumber slumber.generic.seller.get_object_or_404.return_value = self.seller slumber.bango.billing.post.return_value = { 'billingConfigurationId': 'bill_id' } slumber.bango.product.get_object_or_404.side_effect = ( ObjectDoesNotExist) trans_id, pay_url, seller_uuid = self.provider.start_transaction( *range(0, 8)) eq_(trans_id, 'bill_id') def test_with_bango_product(self): slumber = self.slumber slumber.generic.seller.get_object_or_404.return_value = self.seller slumber.bango.billing.post.return_value = { 'billingConfigurationId': 'bill_id' } slumber.bango.product.get_object.return_value = {'resource_uri': 'foo'} trans_id, pay_url, seller_uuid = self.provider.start_transaction( *range(0, 8)) eq_(trans_id, 'bill_id') def test_pay_url(self): bill_id = '123' slumber = self.slumber slumber.generic.seller.get_object_or_404.return_value = self.seller slumber.bango.billing.post.return_value = { 'billingConfigurationId': bill_id } with self.settings(PAY_URLS={ 'bango': { 'base': 'http://bango', 'pay': '/pay?bcid={uid_pay}' } }): trans_id, pay_url, seller_uuid = self.provider.start_transaction( *range(0, 8)) eq_(pay_url, 'http://bango/pay?bcid={b}'.format(b=bill_id))
class TestBango(TestCase): uuid = 'some:pin' seller = {'bango': {'seller': 's', 'resource_uri': 'r', 'package_id': '1234'}, 'resource_uri': '/seller/1', 'resource_pk': 'seller_pk'} def setUp(self): super(TestBango, self).setUp() self.slumber = mock.MagicMock() self.provider = ProviderHelper('bango', slumber=self.slumber) def start(self): return self.provider.start_transaction(*range(0, 9)) def test_create_without_bango_seller(self): self.slumber.generic.seller.get_object.return_value = { 'bango': None, 'resource_pk': '1', 'resource_uri': '/seller/1' } with self.assertRaises(ValueError): self.provider.create_product( external_id='ext:id', product_name=u'Ivan Krsti\u0107', generic_seller={}, provider_seller_uuid='provider_seller_uuid', generic_product={'resource_pk': '2', 'resource_uri': '/foo'}) self.slumber.generic.seller.get_object.assert_called_with( uuid='provider_seller_uuid') def test_create_bango_product(self): slumber = self.slumber slumber.bango.generic.post.return_value = {'product': 'some:uri'} slumber.bango.product.post.return_value = {'resource_uri': 'some:uri', 'bango_id': '5678'} assert self.provider.create_product( external_id='ext:id', product_name=u'Ivan Krsti\u0107', generic_seller=self.seller, provider_seller_uuid='xyz') assert slumber.generic.product.post.called kw = slumber.generic.product.post.call_args[0][0] eq_(kw['external_id'], 'ext:id') eq_(slumber.bango.rating.post.call_count, 2) assert slumber.bango.premium.post.called def test_no_seller(self): slumber = self.slumber slumber.generic.seller.get_object_or_404.side_effect = ( ObjectDoesNotExist) with self.assertRaises(SellerNotConfigured): self.start() def test_no_bango_product(self): slumber = self.slumber slumber.generic.seller.get_object_or_404.return_value = self.seller slumber.bango.billing.post.return_value = { 'billingConfigurationId': 'bill_id'} slumber.bango.product.get_object_or_404.side_effect = ( ObjectDoesNotExist) trans_id, pay_url, seller_uuid = self.start() eq_(trans_id, 'bill_id') def test_with_bango_product(self): slumber = self.slumber slumber.generic.seller.get_object_or_404.return_value = self.seller slumber.bango.billing.post.return_value = { 'billingConfigurationId': 'bill_id'} slumber.bango.product.get_object.return_value = { 'resource_uri': 'foo'} trans_id, pay_url, seller_uuid = self.start() eq_(trans_id, 'bill_id') def test_pay_url(self): bill_id = '123' slumber = self.slumber slumber.generic.seller.get_object_or_404.return_value = self.seller slumber.bango.billing.post.return_value = { 'billingConfigurationId': bill_id} with self.settings( PAY_URLS={'bango': {'base': 'http://bango', 'pay': '/pay?bcid={uid_pay}'}}): trans_id, pay_url, seller_uuid = self.start() eq_(pay_url, 'http://bango/pay?bcid={b}'.format(b=bill_id)) def test_transaction_created(self): slumber = self.slumber slumber.bango.billing.post.return_value = { 'billingConfigurationId': 'uid:pay' } slumber.generic.buyer.get_object_or_404.return_value = { 'resource_pk': '1', 'resource_uri': '/generic/buyer/1/' } slumber.generic.product.get_object_or_404.return_value = { 'external_id': 'ext:id', 'resource_pk': '1', 'resource_uri': '/generic/product/1/' } slumber.generic.seller.get_object_or_404.return_value = { 'resource_pk': '1', 'resource_uri': '/generic/seller/1/' } data = { 'status': constants.STATUS_PENDING, 'source': 'unknown', 'uid_pay': 'uid:pay', 'uuid': 0, 'provider': constants.PROVIDER_BANGO, 'buyer': '/generic/buyer/1/', 'seller_product': '/generic/product/1/', 'type': constants.TYPE_PAYMENT, 'seller': '/generic/seller/1/', } self.start() slumber.generic.transaction.post.assert_called_with(data)
class TestReferenceProvider(ProviderTestCase): def setUp(self): self.slumber = mock.MagicMock() self.provider = ProviderHelper('reference', slumber=self.slumber) def set_mocks(self, returns={}, keys=None, **kw): if not keys: keys = ( 'generic.seller', 'generic.product', 'generic.buyer', 'provider.reference.products', 'provider.reference.transactions', ) return super(TestReferenceProvider, self).set_mocks(returns=returns, keys=keys, **kw) def test_start_with_existing_prod(self): seller_uuid = 'seller-xyz' product_uuid = 'app-xyz' self.set_mocks( { 'provider.reference.transactions': { 'method': 'post', 'return': { 'token': 'zippy-trans-token', } } }, seller_uuid=seller_uuid, product_uuid=product_uuid) trans_id, pay_url, seller_uuid = self.configure( seller_uuid=seller_uuid, product_uuid=product_uuid) eq_(trans_id, 'zippy-trans-token') eq_(seller_uuid, seller_uuid) assert pay_url.endswith('tx={t}'.format( t=trans_id)), ('Unexpected: {url}'.format(url=pay_url)) kw = self.slumber.provider.reference.products\ .get_object_or_404.call_args[1] eq_(kw['external_id'], product_uuid) eq_(kw['seller_id'], seller_uuid) def test_with_new_prod(self): new_product_id = 66 product_uuid = 'app-xyz' seller_uuid = 'seller-xyz' self.set_mocks( { 'generic.product': { 'side_effect': ObjectDoesNotExist, }, 'provider.reference.transactions': { 'method': 'post', 'return': { 'token': 'zippy-trans-token', } }, 'provider.reference.products': { 'side_effect': ObjectDoesNotExist, }, 'provider.reference.sellers': { 'return': { 'resource_pk': seller_uuid, } } }, seller_uuid=seller_uuid) self.slumber.provider.reference.products.post.return_value = { 'resource_pk': new_product_id, } result = self.configure(seller_uuid=seller_uuid, product_uuid=product_uuid) eq_(result[0], 'zippy-trans-token') kw = self.slumber.provider.reference.products.post.call_args[0][0] eq_(kw['external_id'], product_uuid) eq_(kw['seller_id'], seller_uuid) kw = self.slumber.provider.reference.transactions.post.call_args[0][0] eq_(kw['product_id'], new_product_id) eq_(kw['product_image_url'], '/todo/icons') assert kw['success_url'].endswith('/provider/reference/success'), ( 'Unexpected: {0}'.format(kw['success_url'])) assert kw['error_url'].endswith('/provider/reference/error'), ( 'Unexpected: {0}'.format(kw['error_url'])) def test_callback_validation_success(self): self.set_mocks( { 'provider.reference.notices': { 'method': 'post', 'return': { 'result': 'OK', } }, 'provider.reference.transactions': { 'method': 'post', 'return': { 'token': 'zippy-trans-token', } } }, product_uuid='XYZ') self.configure(seller_uuid='seller-xyz', product_uuid='app-xyz') is_valid = self.provider.is_callback_token_valid({'foo': 'bar'}) eq_(is_valid, True) eq_(self.slumber.provider.reference.notices.post.call_args[0][0], {'qs': { 'foo': 'bar' }}) def test_callback_validation_failure(self): self.set_mocks( { 'provider.reference.notices': { 'method': 'post', 'return': { 'result': 'FAIL', 'reason': 'signature mismatch', } }, 'provider.reference.transactions': { 'method': 'post', 'return': { 'token': 'zippy-trans-token', } } }, product_uuid='XYZ') self.configure(seller_uuid='seller-xyz', product_uuid='app-xyz') is_valid = self.provider.is_callback_token_valid({'foo': 'bar'}) eq_(is_valid, False)
def setUp(self): self.slumber = mock.MagicMock() self.provider = ProviderHelper('reference', slumber=self.slumber)
def setUp(self): self.slumber = mock.MagicMock() self.provider = ProviderHelper('boku', slumber=self.slumber)
def configure_transaction(request, trans=None, mcc=None, mnc=None): """ Begins a background task to configure a payment transaction. Returns a tuple of (was_configured, error_code) * was_configured is a boolean * error_code, when not None, is an error code for the failure """ if request.session.get('is_simulation', False): log.info('is_simulation: skipping configure payments step') return (False, None) notes = request.session.get('notes', {}) if mcc and mnc: notes['network'] = {'mnc': mnc, 'mcc': mcc} else: # Reset network state to avoid leakage from previous states. notes['network'] = {} request.session['notes'] = notes log.info('Added mcc/mnc to session: ' '{network}'.format(network=notes['network'])) log.info('configuring transaction {0} from client'.format( request.session.get('trans_id'))) if not trans and 'trans_id' not in request.session: log.error('trans_id: not found in session') return (False, dev_messages.TRANS_MISSING) try: if not trans: trans = client.get_transaction(uuid=request.session['trans_id']) log.info('attempt to reconfigure trans {0} (status={1})'.format( request.session['trans_id'], trans['status'])) except ObjectDoesNotExist: trans = {} if trans.get('status') in constants.STATUS_RETRY_OK: new_trans_id = trans_id() log.info('retrying trans {0} (status={1}) as {2}'.format( request.session['trans_id'], trans['status'], new_trans_id)) request.session['trans_id'] = new_trans_id last_configured = request.session.get('configured_trans') if last_configured == request.session['trans_id']: log.info('trans %s (status=%r) already configured: ' 'skipping configure payments step' % (request.session['trans_id'], trans.get('status'))) return (False, None) # Localize the product before sending it off to solitude/bango. _localize_pay_request(request) log.info('configuring payment in background for trans {t} (status={s}); ' 'Last configured: {c}'.format(t=request.session['trans_id'], s=trans.get('status'), c=last_configured)) network = request.session['notes'].get('network', {}) providers = ProviderHelper.supported_providers( mcc=network.get('mcc'), mnc=network.get('mnc'), ) start_pay.delay(request.session['trans_id'], request.session['notes'], request.session['uuid'], [p.name for p in providers]) # Now that the background task has been started successfully, # prevent configuration from running twice. request.session['configured_trans'] = request.session['trans_id'] return (True, None)