def trans_start_url(request): """ JSON handler to get the Bango payment URL to start a transaction. """ trans = None trans_id = request.session.get('trans_id') data = {'url': None, 'status': None} if not trans_id: log.error('trans_start_url(): no transaction ID in session') return http.HttpResponseBadRequest() try: statsd.incr('purchase.payment_time.retry') with statsd.timer('purchase.payment_time.get_transaction'): trans = solitude.get_transaction(trans_id) data['status'] = trans['status'] data['provider'] = constants.PROVIDERS_INVERTED[trans['provider']] except ObjectDoesNotExist: log.error('trans_start_url() transaction does not exist: {t}' .format(t=trans_id)) if data['status'] == constants.STATUS_PENDING: statsd.incr('purchase.payment_time.success') payment_start = request.session.get('payment_start', False) if payment_start: delta = int((time.time() - float(payment_start)) * 1000) statsd.timing('purchase.payment_time.duration', delta) url = get_payment_url(trans) log.info('async call got payment URL {url} for trans {tr}' .format(url=url, tr=trans)) data['url'] = url return data
def wait_to_start(request): """ Wait until the transaction is in a ready state. The transaction was started previously during the buy flow in the background from webpay.pay.tasks. Serve JS that polls for transaction state. When ready, redirect to the Bango payment URL using the generated billing configuration ID. """ trans_id = request.session.get('trans_id', None) if not trans_id: # This seems like a seriously problem but maybe there is just a race # condition. If we see a lot of these in the logs it means the # payment will never complete so we should keep an eye on it. log.error('wait_to_start() session trans_id was None') try: trans = solitude.get_transaction(trans_id) except ObjectDoesNotExist: trans = {'status': None} if trans['status'] in constants.STATUS_ENDED: log.exception('Attempt to restart finished transaction {0} ' 'with status {1}'.format(trans_id, trans['status'])) return _error(request, msg=_('Transaction has already ended.')) if trans['status'] == constants.STATUS_PENDING: # Dump any messages so we don't show them later. clear_messages(request) # The transaction is ready; no need to wait for it. return http.HttpResponseRedirect(_bango_start_url(trans['uid_pay'])) return render(request, 'pay/wait-to-start.html')
def test_notes_issuer_transactions(self, slumber): iss = Issuer.objects.create() slumber.generic.transaction.get.return_value = {'objects': [ {'notes': json.dumps({'issuer': iss.pk})} ]} trans = client.get_transaction('x') eq_(trans['notes']['issuer'], iss)
def trans_start_url(request): """ JSON handler to get the Bango payment URL to start a transaction. """ try: statsd.incr('purchase.payment_time.retry') with statsd.timer('purchase.payment_time.get_transaction'): trans = solitude.get_transaction(request.session['trans_id']) except ObjectDoesNotExist: log.error('trans_start_url() transaction does not exist: {t}' .format(t=request.session['trans_id'])) trans = {'status': None} data = {'url': None, 'status': trans['status']} if trans['status'] == constants.STATUS_PENDING: statsd.incr('purchase.payment_time.success') payment_start = request.session.get('payment_start', False) if payment_start: delta = int((time.time() - float(payment_start)) * 1000) statsd.timing('purchase.payment_time.duration', delta) url = get_payment_url(trans) log.info('async call got payment URL {url} for trans {tr}' .format(url=url, tr=trans)) data['url'] = url return data
def lobby(request): sess = request.session trans = None have_jwt = bool(request.GET.get('req')) log.info('starting from JWT? {have_jwt}'.format(have_jwt=have_jwt)) if have_jwt: # If it returns a response there was likely # an error and we should return it. res = process_pay_req(request) if isinstance(res, http.HttpResponse): return res elif settings.TEST_PIN_UI: # This won't get you very far but it lets you create/enter PINs # and stops a traceback after that. sess['trans_id'] = trans_id() elif not sess.get('is_simulation', False): try: trans = solitude.get_transaction(sess.get('trans_id')) except (ObjectDoesNotExist, HttpClientError), exc: if sess.get('trans_id'): log.info('Attempted to restart non-existent transaction ' '{trans}; exc={exc}' .format(trans=sess.get('trans_id'), exc=exc)) return system_error(request, code=msg.BAD_REQUEST) log.info('Re-used existing transaction ID: {tx}' .format(tx=sess.get('trans_id')))
def wait_to_start(request): """ Wait until the transaction is in a ready state. The transaction was started previously during the buy flow in the background from webpay.pay.tasks. Serve JS that polls for transaction state. When ready, redirect to the Bango payment URL using the generated billing configuration ID. """ try: trans = solitude.get_transaction(request.session['trans_id']) except ObjectDoesNotExist: trans = {'status': None} if trans['status'] in constants.STATUS_ENDED: log.exception('Attempt to restart finished transaction.') return _error(request, msg=_('Transaction has already ended.')) if trans['status'] == constants.STATUS_PENDING: # Dump any messages so we don't show them later. clear_messages(request) # The transaction is ready; no need to wait for it. return http.HttpResponseRedirect(_bango_start_url(trans['uid_pay'])) return render(request, 'pay/wait-to-start.html')
def lobby(request): sess = request.session trans = None if request.GET.get('req'): # If it returns a response there was likely # an error and we should return it. res = process_pay_req(request) if isinstance(res, http.HttpResponse): return res elif settings.TEST_PIN_UI: # This won't get you very far but it lets you create/enter PINs # and stops a traceback after that. request.session['trans_id'] = trans_id() elif not sess.get('is_simulation', False): try: trans = solitude.get_transaction(request.session.get('trans_id')) except ObjectDoesNotExist: if request.session.get('trans_id'): log.info('Attempted to restart non-existent transaction {0}' .format(request.session.get('trans_id'))) return _error(request, msg='req is required') pin_form = VerifyPinForm() if sess.get('uuid'): auth_utils.update_session(request, sess.get('uuid')) # Before we continue with the buy flow, let's save some # time and get the transaction configured via Bango in the # background. log.info('configuring transaction {0} from lobby' .format(request.session.get('trans_id'))) tasks.configure_transaction(request, trans=trans) redirect_url = check_pin_status(request) if redirect_url is not None: return http.HttpResponseRedirect(redirect_url) # If the buyer closed the trusted UI during reset flow, we want to unset # the reset pin flag. They can hit the forgot pin button if they still # don't remember their pin. if sess.get('uuid_needs_pin_reset'): solitude.set_needs_pin_reset(sess['uuid'], False) sess['uuid_needs_pin_reset'] = False if sess.get('is_simulation', False): sim_req = sess['notes']['pay_request']['request']['simulate'] log.info('Starting simulate %s for %s' % (sim_req, sess['notes']['issuer_key'])) return render(request, 'pay/simulate.html', { 'simulate': sim_req }) return render(request, 'pay/lobby.html', { 'action': reverse('pin.verify'), 'form': pin_form, 'title': _('Enter Pin') })
def lobby(request): sess = request.session trans = None if request.GET.get('req'): # If it returns a response there was likely # an error and we should return it. res = process_pay_req(request) if isinstance(res, http.HttpResponse): return res elif settings.TEST_PIN_UI: # This won't get you very far but it lets you create/enter PINs # and stops a traceback after that. sess['trans_id'] = trans_id() elif not sess.get('is_simulation', False): try: trans = solitude.get_transaction(sess.get('trans_id')) except ObjectDoesNotExist: if sess.get('trans_id'): log.info( 'Attempted to restart non-existent transaction {0}'.format( sess.get('trans_id'))) return _error(request, msg='req is required') pin_form = VerifyPinForm() if sess.get('uuid'): auth_utils.update_session(request, sess.get('uuid'), False) # Before we continue with the buy flow, let's save some # time and get the transaction configured via Bango in the # background. log.info('configuring transaction {0} from lobby'.format( sess.get('trans_id'))) tasks.configure_transaction(request, trans=trans) redirect_url = check_pin_status(request) if redirect_url is not None: return http.HttpResponseRedirect('{0}?next={1}'.format( reverse('pay.bounce'), redirect_url)) # If the buyer closed the trusted UI during reset flow, we want to unset # the reset pin flag. They can hit the forgot pin button if they still # don't remember their pin. if sess.get('uuid_needs_pin_reset'): solitude.set_needs_pin_reset(sess['uuid'], False) sess['uuid_needs_pin_reset'] = False if sess.get('is_simulation', False): sim_req = sess['notes']['pay_request']['request']['simulate'] log.info('Starting simulate %s for %s' % (sim_req, sess['notes']['issuer_key'])) return render(request, 'pay/simulate.html', {'simulate': sim_req}) return render(request, 'pay/lobby.html', { 'action': reverse('pin.verify'), 'form': pin_form, 'title': _('Enter Pin') })
def test_notes_transactions(self, slumber): slumber.generic.transaction.get.return_value = { 'objects': [{ 'notes': json.dumps({'foo': 'bar'}) }] } trans = client.get_transaction('x') eq_(trans['notes'], {'foo': 'bar'})
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 test_notes_issuer_transactions(self, slumber): iss = Issuer.objects.create() slumber.generic.transaction.get.return_value = { 'objects': [{ 'notes': json.dumps({'issuer': iss.pk}) }] } trans = client.get_transaction('x') eq_(trans['notes']['issuer'], iss)
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 chargeback_notify(transaction_uuid, **kw): """ Notify the app of a chargeback by posting a JWT. The JWT sent is the same as for payment notifications with the addition of response.reason, explained below. trans_id: pk of Transaction reason: either 'reversal' or 'refund' """ transaction = client.get_transaction(transaction_uuid) _notify(chargeback_notify, transaction, extra_response={"reason": kw.get("reason", "")})
def trans_start_url(request): """ JSON handler to get the Bango payment URL to start a transaction. """ try: trans = solitude.get_transaction(request.session['trans_id']) except ObjectDoesNotExist: trans = {'status': None} data = {'url': None, 'status': trans['status']} if trans['status'] == constants.STATUS_PENDING: data['url'] = _bango_start_url(trans['uid_pay']) return data
def trans_start_url(request): """ JSON handler to get the Bango payment URL to start a transaction. """ try: trans = solitude.get_transaction(request.session['trans_id']) except ValueError: trans = {'status': None} data = {'url': None, 'status': trans['status']} if trans['status'] == constants.STATUS_PENDING: data['url'] = settings.BANGO_PAY_URL % trans['uid_pay'] return data
def chargeback_notify(transaction_uuid, **kw): """ Notify the app of a chargeback by posting a JWT. The JWT sent is the same as for payment notifications with the addition of response.reason, explained below. trans_id: pk of Transaction reason: either 'reversal' or 'refund' """ transaction = client.get_transaction(transaction_uuid) _notify(chargeback_notify, transaction, extra_response={'reason': kw.get('reason', '')})
def retrieve(self, request): try: trans_id = request.session["trans_id"] transaction = client.get_transaction(uuid=trans_id) except ObjectDoesNotExist: return response.Response( {"error_code": "TRANSACTION_NOT_FOUND", "error": "Transaction could not be found."}, status=404 ) except KeyError: return response.Response({"error_code": "TRANS_ID_NOT_SET", "error": "trans_id was not set."}, status=400) else: serializer = TransactionSerializer(transaction) return response.Response(serializer.data)
def payment_notify(transaction_uuid, **kw): """ Notify the app of a successful payment by posting a JWT. The JWT sent is a mirror of the JWT used by the app to request payment except that it includes the following: :param response.transactionID: which is the Solitude transaction UUID. :param response.price: object that contains the amount and currency the customer actually paid in. """ transaction = client.get_transaction(transaction_uuid) _notify(payment_notify, transaction)
def trans_start_url(request): """ JSON handler to get the Bango payment URL to start a transaction. """ try: trans = solitude.get_transaction(request.session['trans_id']) except ValueError: trans = {'status': None} data = {'url': None, 'status': trans['status']} if trans['status'] == constants.STATUS_PENDING: data['url'] = settings.BANGO_PAY_URL % trans['uid_pay'] # TODO(Wraithan): We should catch if a user is trying to restart an expired # or completed transaction. (bug 829750). # This will timeout in the client until then. return data
def payment_notify(transaction_uuid, **kw): """ Notify the app of a successful payment by posting a JWT. The JWT sent is a mirror of the JWT used by the app to request payment except that it includes the following: - A response.transactionID which is the Marketplace transaction ID - The price array only includes one entry, the actual price / currency that the customer paid in. The original request would include all possible prices / currencies. trans_id: pk of Transaction """ transaction = client.get_transaction(transaction_uuid) _notify(payment_notify, transaction)
def configure_transaction(request, trans=None): """ Begins a background task to configure a payment transaction. """ if settings.FAKE_PAYMENTS: log.info('FAKE_PAYMENTS: skipping configure payments step') return if request.session.get('is_simulation', False): log.info('is_simulation: skipping configure payments step') return 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 if request.session.get('configured_trans') == request.session['trans_id']: log.info('trans %s (status=%r) already configured: ' 'skipping configure payments step' % (request.session['trans_id'], trans.get('status'))) return # 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 {0} (status={1})' .format(request.session['trans_id'], trans.get('trans_id'))) start_pay.delay(request.session['trans_id'], request.session['notes'], request.session['uuid']) # 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']
def retrieve(self, request): try: trans_id = request.session['trans_id'] transaction = client.get_transaction(uuid=trans_id) except ObjectDoesNotExist: return response.Response({ 'error_code': 'TRANSACTION_NOT_FOUND', 'error': 'Transaction could not be found.', }, status=404) except KeyError: return response.Response({ 'error_code': 'TRANS_ID_NOT_SET', 'error': 'trans_id was not set.', }, status=400) else: serializer = TransactionSerializer(transaction) return response.Response(serializer.data)
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 wait_to_start(request): """ Wait until the transaction is in a ready state. Serve JS that polls for transaction state. When ready, redirect to the Bango payment URL using the generated billing configuration ID. """ try: trans = solitude.get_transaction(request.session['trans_id']) except ValueError: trans = {'status': None} if trans['status'] == constants.STATUS_PENDING: # The transaction is ready; no need to wait for it. return http.HttpResponseRedirect( settings.BANGO_PAY_URL % trans['uid_pay']) return render(request, 'pay/wait-to-start.html')
def send_simulated_notification(trans_id): try: trans = solitude.get_transaction(trans_id) except ObjectDoesNotExist: # This could mean: # * the transaction was never configured due to error. # * the celery configuration task was just too slow. raise ValueError( 'Cannot simulate transaction {t}, not configured' .format(t=trans_id)) # TODO: patch solitude to mark this as a super-simulated # transaction. req = trans['notes']['pay_request'] # TODO: support simulating refunds. req['request']['simulate'] = {'result': 'postback'} tasks.simulate_notify.delay(trans['notes']['issuer_key'], req)
def configure_transaction(request, trans=None): """ Begins a background task to configure a payment transaction. """ if settings.FAKE_PAYMENTS: log.info('FAKE_PAYMENTS: skipping configure payments step') return if request.session.get('is_simulation', False): log.info('is_simulation: skipping configure payments step') return 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 if request.session.get('configured_trans') == request.session['trans_id']: log.info('trans %s (status=%r) already configured: ' 'skipping configure payments step' % (request.session['trans_id'], trans.get('status'))) return # 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 {0} (status={1})'.format( request.session['trans_id'], trans.get('trans_id'))) start_pay.delay(request.session['trans_id'], request.session['notes'], request.session['uuid']) # 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']
def wait_to_start(request): """ Wait until the transaction is in a ready state. The transaction was started previously during the buy flow in the background from webpay.pay.tasks. Serve JS that polls for transaction state. When ready, redirect to the Bango payment URL using the generated billing configuration ID. """ trans_id = request.session.get('trans_id', None) if not trans_id: # This seems like a seriously problem but maybe there is just a race # condition. If we see a lot of these in the logs it means the # payment will never complete so we should keep an eye on it. log.error('wait_to_start() session trans_id {t} was None' .format(t=trans_id)) try: statsd.incr('purchase.payment_time.retry') with statsd.timer('purchase.payment_time.get_transation'): trans = solitude.get_transaction(trans_id) except ObjectDoesNotExist: trans = {'status': None} if trans['status'] in constants.STATUS_ENDED: statsd.incr('purchase.payment_time.failure') log.exception('Attempt to restart finished transaction {0} ' 'with status {1}'.format(trans_id, trans['status'])) return system_error(request, code=msg.TRANS_ENDED) if trans['status'] == constants.STATUS_PENDING: statsd.incr('purchase.payment_time.success') payment_start = request.session.get('payment_start', False) if payment_start: delta = int((time.time() - float(payment_start)) * 1000) statsd.timing('purchase.payment_time.duration', delta) # Dump any messages so we don't show them later. clear_messages(request) # The transaction is ready; no need to wait for it. url = get_payment_url(trans) log.info('immediately redirecting to payment URL {url} ' 'for trans {tr}'.format(url=url, tr=trans)) return http.HttpResponseRedirect(url) return render(request, 'pay/wait-to-start.html')
def trans_start_url(request): """ JSON handler to get the Bango payment URL to start a transaction. """ try: statsd.incr('purchase.payment_time.retry') with statsd.timer('purchase.payment_time.get_transation'): trans = solitude.get_transaction(request.session['trans_id']) except ObjectDoesNotExist: trans = {'status': None} data = {'url': None, 'status': trans['status']} if trans['status'] == constants.STATUS_PENDING: statsd.incr('purchase.payment_time.success') payment_start = request.session.get('payment_start', False) if payment_start: delta = int((time.time() - float(payment_start)) * 1000) statsd.timing('purchase.payment_time.duration', delta) data['url'] = provider.get_start_url(trans['uid_pay']) return data
def trans_start_url(request): """ JSON handler to get the provider payment URL to start a transaction. """ trans = None trans_id = request.session.get('trans_id') data = {'url': None, 'status': None, 'provider': None} if not trans_id: log.error('trans_start_url(): no transaction ID in session') return http.HttpResponseBadRequest() try: statsd.incr('purchase.payment_time.retry') with statsd.timer('purchase.payment_time.get_transaction'): trans = client.get_transaction(trans_id) data['status'] = trans['status'] data['provider'] = constants.PROVIDERS_INVERTED.get(trans['provider']) except ObjectDoesNotExist: log.error('trans_start_url() transaction does not exist: {t}' .format(t=trans_id)) if data['status'] == constants.STATUS_PENDING: statsd.incr('purchase.payment_time.success') payment_start = request.session.get('payment_start', False) if payment_start: delta = int((time.time() - float(payment_start)) * 1000) statsd.timing('purchase.payment_time.duration', delta) url = get_payment_url(trans) log.info('async call got payment URL {url} for trans {tr}' .format(url=url, tr=trans)) data['url'] = url if trans and trans['status'] == constants.STATUS_ERRORED: statsd.incr('purchase.payment_time.errored') log.exception('Purchase configuration failed: {0} with status {1}' .format(trans_id, trans['status'])) return system_error( request, code=getattr(msg, trans.get('status_reason', 'UNEXPECTED_ERROR')) ) return data
def wait_to_start(request): """ Wait until the transaction is in a ready state. The transaction was started previously during the buy flow in the background from webpay.pay.tasks. Serve JS that polls for transaction state. When ready, redirect to the Bango payment URL using the generated billing configuration ID. """ trans_id = request.session.get('trans_id', None) if not trans_id: # This seems like a seriously problem but maybe there is just a race # condition. If we see a lot of these in the logs it means the # payment will never complete so we should keep an eye on it. log.error( 'wait_to_start() session trans_id {t} was None'.format(t=trans_id)) try: statsd.incr('purchase.payment_time.retry') with statsd.timer('purchase.payment_time.get_transation'): trans = solitude.get_transaction(trans_id) except ObjectDoesNotExist: trans = {'status': None} if trans['status'] in constants.STATUS_ENDED: statsd.incr('purchase.payment_time.failure') log.exception('Attempt to restart finished transaction {0} ' 'with status {1}'.format(trans_id, trans['status'])) return system_error(request, code=msg.TRANS_ENDED) if trans['status'] == constants.STATUS_PENDING: statsd.incr('purchase.payment_time.success') payment_start = request.session.get('payment_start', False) if payment_start: delta = int((time.time() - float(payment_start)) * 1000) statsd.timing('purchase.payment_time.duration', delta) # Dump any messages so we don't show them later. clear_messages(request) # The transaction is ready; no need to wait for it. return http.HttpResponseRedirect(get_payment_url(trans)) return render(request, 'pay/wait-to-start.html')
def super_simulate(request): if not settings.ALLOW_ADMIN_SIMULATIONS: return http.HttpResponseForbidden() if request.method == "POST": try: trans = solitude.get_transaction(request.session["trans_id"]) except ObjectDoesNotExist: # If this happens a lot and the celery task is just slow, # we might need to make a polling loop. raise ValueError("Cannot simulate transaction {0}, not configured".format(request.session.get["trans_id"])) # TODO: patch solitude to mark this as a super-simulated transaction. req = trans["notes"]["pay_request"] # TODO: support simulating refunds. req["request"]["simulate"] = {"result": "postback"} tasks.simulate_notify.delay(trans["notes"]["issuer_key"], req) return render(request, "pay/simulate_done.html", {}) return render(request, "pay/super_simulate.html")
def super_simulate(request): if not settings.ALLOW_ADMIN_SIMULATIONS: return http.HttpResponseForbidden() if request.method == 'POST': try: trans = solitude.get_transaction(request.session['trans_id']) except ObjectDoesNotExist: # If this happens a lot and the celery task is just slow, # we might need to make a polling loop. raise ValueError('Cannot simulate transaction {0}, not configured' .format(request.session.get['trans_id'])) # TODO: patch solitude to mark this as a super-simulated transaction. req = trans['notes']['pay_request'] # TODO: support simulating refunds. req['request']['simulate'] = {'result': 'postback'} tasks.simulate_notify.delay(trans['notes']['issuer_key'], req) return render(request, 'pay/simulate_done.html', {}) return render(request, 'pay/super_simulate.html')
def trans_start_url(request): """ JSON handler to get the Bango payment URL to start a transaction. """ try: statsd.incr("purchase.payment_time.retry") with statsd.timer("purchase.payment_time.get_transaction"): trans = solitude.get_transaction(request.session["trans_id"]) except ObjectDoesNotExist: log.error("trans_start_url() transaction does not exist: {t}".format(t=request.session["trans_id"])) trans = {"status": None} data = {"url": None, "status": trans["status"]} if trans["status"] == constants.STATUS_PENDING: statsd.incr("purchase.payment_time.success") payment_start = request.session.get("payment_start", False) if payment_start: delta = int((time.time() - float(payment_start)) * 1000) statsd.timing("purchase.payment_time.duration", delta) data["url"] = get_payment_url(trans) return data
def transaction_status(request, transaction_uuid): """ Given a Solitude transaction UUID, return its status. This returns a NULL URL for compatibility with another view that redirects to begin payment. """ if request.session.get("trans_id") != transaction_uuid: log.info( "Cannot get transaction status for {t}; session: {s}".format(t=transaction_uuid, s=repr(request.session)) ) info = "Transaction query string param {t} did not match " "transaction in session".format(t=transaction_uuid) log_cef(info, request, severity=7) return HttpResponseForbidden() try: trans = client.get_transaction(transaction_uuid) return {"status": trans["status"], "url": None, "provider": PROVIDERS_INVERTED[trans["provider"]]} except ObjectDoesNotExist: log.info("Cannot get transaction status; not found: {t}".format(t=transaction_uuid)) return HttpResponseNotFound()
def wait_to_start(request): """ Wait until the transaction is in a ready state. Serve JS that polls for transaction state. When ready, redirect to the Bango payment URL using the generated billing configuration ID. """ try: trans = solitude.get_transaction(request.session['trans_id']) except ValueError: trans = {'status': None} if trans['status'] in constants.STATUS_ENDED: log.exception('Attempt to restart finished transaction.') return _error(request, msg=_('Transaction has already ended.')) if trans['status'] == constants.STATUS_PENDING: # The transaction is ready; no need to wait for it. return http.HttpResponseRedirect(_bango_start_url(trans['uid_pay'])) return render(request, 'pay/wait-to-start.html')
def configure_transaction(request): """ Begins a background task to configure a payment transaction. """ if settings.FAKE_PAYMENTS: log.info('FAKE_PAYMENTS: skipping configure payments step') return if request.session['is_simulation']: log.info('is_simulation: skipping configure payments step') return try: trans = client.get_transaction(uuid=request.session['trans_id']) if trans['status'] == constants.STATUS_PENDING: log.info('trans %s (status=%r) already configured: ' 'skipping configure payments step' % (request.session['trans_id'], trans['status'])) return elif trans['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 else: raise TransactionOutOfSync('cannot configure transaction {0}, ' 'status={1}'.format( request.session['trans_id'], trans['status'])) except ObjectDoesNotExist: pass # Localize the product before sending it off to solitude/bango. _localize_pay_request(request) log.info('configuring payment in background for trans {0}' .format(request.session['trans_id'])) start_pay.delay(request.session['trans_id'], request.session['notes'], request.session['uuid'])
def transaction_status(request, transaction_uuid): """ Given a Solitude transaction UUID, return its status. This returns a NULL URL for compatibility with another view that redirects to begin payment. """ if request.session.get('trans_id') != transaction_uuid: log.info('Cannot get transaction status for {t}; session: {s}' .format(t=transaction_uuid, s=repr(request.session))) info = ('Transaction query string param {t} did not match ' 'transaction in session'.format(t=transaction_uuid)) log_cef(info, request, severity=7) return HttpResponseForbidden() try: trans = client.get_transaction(transaction_uuid) return {'status': trans['status'], 'url': None} except ObjectDoesNotExist: log.info('Cannot get transaction status; not found: {t}' .format(t=transaction_uuid)) return HttpResponseNotFound()
def trans_start_url(request): """ JSON handler to get the Bango payment URL to start a transaction. """ try: statsd.incr('purchase.payment_time.retry') with statsd.timer('purchase.payment_time.get_transaction'): trans = solitude.get_transaction(request.session['trans_id']) except ObjectDoesNotExist: log.error('trans_start_url() transaction does not exist: {t}'.format( t=request.session['trans_id'])) trans = {'status': None} data = {'url': None, 'status': trans['status']} if trans['status'] == constants.STATUS_PENDING: statsd.incr('purchase.payment_time.success') payment_start = request.session.get('payment_start', False) if payment_start: delta = int((time.time() - float(payment_start)) * 1000) statsd.timing('purchase.payment_time.duration', delta) data['url'] = get_payment_url(trans) return data
def super_simulate(request): if not settings.ALLOW_ADMIN_SIMULATIONS: return http.HttpResponseForbidden() if request.method == 'POST': try: trans = solitude.get_transaction(request.session['trans_id']) except ObjectDoesNotExist: # If this happens a lot and the celery task is just slow, # we might need to make a polling loop. raise ValueError( 'Cannot simulate transaction {0}, not configured'.format( request.session.get['trans_id'])) # TODO: patch solitude to mark this as a super-simulated transaction. req = trans['notes']['pay_request'] # TODO: support simulating refunds. req['request']['simulate'] = {'result': 'postback'} tasks.simulate_notify.delay(trans['notes']['issuer_key'], req) return render(request, 'pay/simulate_done.html', {}) return render(request, 'pay/super_simulate.html')
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)
def test_multiple_transactions(self, slumber): slumber.generic.transaction.get.return_value = {'objects': [1, 2]} with self.assertRaises(ValueError): client.get_transaction('x')