Esempio n. 1
0
def more_info(request: Request, sep6: bool = False) -> Response:
    try:
        request_transaction = _get_transaction_from_request(request, sep6=sep6)
    except (AttributeError, ValidationError) as exc:
        return render_error_response(str(exc), content_type="text/html")
    except Transaction.DoesNotExist:
        return render_error_response(
            _("transaction not found"),
            status_code=status.HTTP_404_NOT_FOUND,
            content_type="text/html",
        )

    serializer = TransactionSerializer(request_transaction,
                                       context={
                                           "request": request,
                                           "sep6": sep6
                                       })
    tx_json = json.dumps({"transaction": serializer.data})
    context = {
        "tx_json": tx_json,
        "amount_in": serializer.data.get("amount_in"),
        "amount_out": serializer.data.get("amount_out"),
        "amount_fee": serializer.data.get("amount_fee"),
        "transaction": request_transaction,
        "asset_code": request_transaction.asset.code,
    }
    if request_transaction.kind == Transaction.KIND.deposit:
        content = rdi.content_for_template(Template.MORE_INFO,
                                           transaction=request_transaction)
        if request_transaction.status == Transaction.STATUS.pending_user_transfer_start:
            context.update(instructions=rdi.instructions_for_pending_deposit(
                request_transaction))
    else:
        content = rwi.content_for_template(Template.MORE_INFO,
                                           transaction=request_transaction)
    if content:
        context.update(content)

    if registered_scripts_func is not scripts:
        logger.warning(
            "DEPRECATED: the `scripts` Polaris integration function will be "
            "removed in Polaris 2.0 in favor of allowing the anchor to override "
            "and extend Polaris' Django templates. See the Template Extensions "
            "documentation for more information.")
    context["scripts"] = registered_scripts_func(content)

    # more_info.html will update the 'callback' parameter value to 'success' after
    # making the callback. If the page is then reloaded, the callback is not included
    # in the rendering context, ensuring only one successful callback request is made.
    callback = request.GET.get("callback")
    if callback and callback != "success":
        context["callback"] = callback

    return Response(context, template_name="polaris/more_info.html")
Esempio n. 2
0
def more_info(request: Request, sep6: bool = False) -> Response:
    try:
        request_transaction = _get_transaction_from_request(request, sep6=sep6)
    except (AttributeError, ValidationError) as exc:
        return render_error_response(str(exc), content_type="text/html")
    except Transaction.DoesNotExist:
        return render_error_response(
            _("transaction not found"),
            status_code=status.HTTP_404_NOT_FOUND,
            content_type="text/html",
        )

    serializer = TransactionSerializer(request_transaction,
                                       context={
                                           "request": request,
                                           "sep6": sep6
                                       })
    tx_json = json.dumps({"transaction": serializer.data})
    resp_data = {
        "tx_json": tx_json,
        "amount_in": serializer.data.get("amount_in"),
        "amount_out": serializer.data.get("amount_out"),
        "amount_fee": serializer.data.get("amount_fee"),
        "transaction": request_transaction,
        "asset_code": request_transaction.asset.code,
    }
    if request_transaction.kind == Transaction.KIND.deposit:
        content = rdi.content_for_template(Template.MORE_INFO,
                                           transaction=request_transaction)
        if request_transaction.status == Transaction.STATUS.pending_user_transfer_start:
            resp_data["instructions"] = rdi.instructions_for_pending_deposit(
                request_transaction)
    else:
        content = rwi.content_for_template(Template.MORE_INFO,
                                           transaction=request_transaction)

    resp_data["scripts"] = registered_scripts_func(content)
    if content:
        resp_data.update(content)

    callback = request.GET.get("callback")
    if callback:
        resp_data["callback"] = callback

    return Response(resp_data, template_name="transaction/more_info.html")
Esempio n. 3
0
def more_info(request: Request) -> Response:
    """
    Popup to display more information about a specific transaction.
    See table: https://github.com/stellar/stellar-protocol/blob/master/ecosystem/sep-0024.md#4-customer-information-status
    """
    try:
        request_transaction = _get_transaction_from_request(request)
    except (AttributeError, ValidationError) as exc:
        return render_error_response(str(exc), content_type="text/html")
    except Transaction.DoesNotExist:
        return render_error_response(
            _("transaction not found"),
            status_code=status.HTTP_404_NOT_FOUND,
            content_type="text/html",
        )

    serializer = TransactionSerializer(
        request_transaction, context={"request": request}
    )
    tx_json = json.dumps({"transaction": serializer.data})
    resp_data = {
        "tx_json": tx_json,
        "transaction": request_transaction,
        "asset_code": request_transaction.asset.code,
        "scripts": registered_scripts_func(None),
    }

    callback = request.GET.get("callback")
    if callback:
        resp_data["callback"] = callback

    if (
        request_transaction.kind == Transaction.KIND.deposit
        and request_transaction.status == Transaction.STATUS.pending_user_transfer_start
    ):
        resp_data["instructions"] = rdi.instructions_for_pending_deposit(
            request_transaction
        )
    return Response(resp_data, template_name="transaction/more_info.html")
Esempio n. 4
0
def get_interactive_deposit(request: Request) -> Response:
    """
    GET /transactions/deposit/webapp

    This endpoint retrieves the next form to be served to the user in the
    interactive flow. The following steps are taken during this process:

        1. URL arguments are parsed and validated.
        2. interactive_url() is called to determine whether or not the anchor
           uses an external service for the interactive flow. If a URL is
           returned, this function redirects to the URL. However, the session
           cookie should still be included in the response so future calls to
           GET /transactions/deposit/interactive/complete are authenticated.
        3. content_for_transaction() is called to retrieve the next form to
           render to the user. `amount` is prepopulated in the form if it was
           passed as a parameter to this endpoint and the form is a subclass
           of TransactionForm.
        4. get and post URLs are constructed with the appropriate arguments
           and passed to the response to be rendered to the user.
    """
    args_or_error = interactive_args_validation(request)
    if "error" in args_or_error:
        return args_or_error["error"]

    transaction = args_or_error["transaction"]
    asset = args_or_error["asset"]
    callback = args_or_error["callback"]
    amount = args_or_error["amount"]

    url = rdi.interactive_url(request, transaction, asset, amount, callback)
    if url:  # The anchor uses a standalone interactive flow
        return redirect(url)

    content = rdi.content_for_transaction(transaction)
    if not content:
        logger.error(
            "The anchor did not provide content, unable to serve page.")
        if transaction.status != transaction.STATUS.incomplete:
            return render_error_response(
                _("The anchor did not provide content, is the interactive flow already complete?"
                  ),
                status_code=422,
                content_type="text/html",
            )
        return render_error_response(
            _("The anchor did not provide content, unable to serve page."),
            status_code=500,
            content_type="text/html",
        )

    scripts = registered_scripts_func(content)

    if content.get("form"):
        try:
            form_class, form_args = content.get("form")
        except TypeError:
            logger.exception(
                "content_for_transaction(): 'form' key value must be a tuple")
            return render_error_response(
                _("The anchor did not provide content, unable to serve page."),
                content_type="text/html",
            )
        is_transaction_form = issubclass(form_class, TransactionForm)
        if is_transaction_form:
            content["form"] = form_class(asset,
                                         initial={"amount": amount},
                                         test_value="103",
                                         **form_args)
        else:
            content["form"] = form_class(**form_args)

    url_args = {"transaction_id": transaction.id, "asset_code": asset.code}
    if callback:
        url_args["callback"] = callback
    if amount:
        url_args["amount"] = amount

    post_url = f"{reverse('post_interactive_deposit')}?{urlencode(url_args)}"
    get_url = f"{reverse('get_interactive_deposit')}?{urlencode(url_args)}"
    content.update(post_url=post_url, get_url=get_url, scripts=scripts)

    return Response(content, template_name="deposit/form.html")
Esempio n. 5
0
def post_interactive_deposit(request: Request) -> Response:
    """
    POST /transactions/deposit/webapp

    This endpoint processes form submissions during the deposit interactive
    flow. The following steps are taken during this process:

        1. URL arguments are parsed and validated.
        2. form_for_transaction() is called to retrieve the form used to
           submit this request. This function is implemented by the anchor.
        3. The form is used to validate the data submitted, and if the form
           is a TransactionForm, the fee for the transaction is calculated.
        4. after_form_validation() is called to allow the anchor to process
           the data submitted. This function should change the application
           state such that the next call to form_for_transaction() returns
           the next form in the flow.
        5. form_for_transaction() is called again to retrieve the next
           form to be served to the user. If a form is returned, the
           function redirects to GET /transaction/deposit/webapp. Otherwise,
           The user's session is invalidated, the transaction status is
           updated, and the function redirects to GET /more_info.
    """
    args_or_error = interactive_args_validation(request)
    if "error" in args_or_error:
        return args_or_error["error"]

    transaction = args_or_error["transaction"]
    asset = args_or_error["asset"]
    callback = args_or_error["callback"]
    amount = args_or_error["amount"]

    form = rdi.form_for_transaction(transaction, post_data=request.POST)
    if not form:
        logger.error("Initial form_for_transaction() call returned None in "
                     f"POST request for transaction: {transaction.id}")
        if transaction.status != transaction.STATUS.incomplete:
            return render_error_response(
                _("The anchor did not provide content, is the interactive flow already complete?"
                  ),
                status_code=422,
                content_type="text/html",
            )
        return render_error_response(
            _("The anchor did not provide form content, unable to serve page."
              ),
            status_code=500,
            content_type="text/html",
        )

    if not form.is_bound:
        # The anchor must initialize the form with the request.POST data
        logger.error(
            "form returned was not initialized with POST data, returning 500")
        return render_error_response(
            _("Unable to validate form submission."),
            status_code=500,
            content_type="text/html",
        )

    if form.is_valid():
        if issubclass(form.__class__, TransactionForm):
            transaction.amount_in = form.cleaned_data["amount"]
            transaction.save()

        rdi.after_form_validation(form, transaction)
        next_form = rdi.form_for_transaction(transaction)
        if next_form or rdi.content_for_template(
                Template.DEPOSIT, form=next_form, transaction=transaction):
            args = {"transaction_id": transaction.id, "asset_code": asset.code}
            if amount:
                args["amount"] = amount
            if callback:
                args["callback"] = callback
            url = reverse("get_interactive_deposit")
            return redirect(f"{url}?{urlencode(args)}")

        else:  # Last form has been submitted
            logger.info(
                f"Finished data collection and processing for transaction {transaction.id}"
            )
            invalidate_session(request)
            transaction.status = Transaction.STATUS.pending_user_transfer_start
            transaction.save()
            url = reverse("more_info")
            args = urlencode({"id": transaction.id, "callback": callback})
            return redirect(f"{url}?{args}")

    else:
        content = (rdi.content_for_template(
            Template.DEPOSIT, form=form, transaction=transaction) or {})
        if registered_scripts_func is not scripts:
            logger.warning(
                "DEPRECATED: the `scripts` Polaris integration function will be "
                "removed in Polaris 2.0 in favor of allowing the anchor to override "
                "and extend Polaris' Django templates. See the Template Extensions "
                "documentation for more information.")
        template_scripts = registered_scripts_func({"form": form, **content})

        url_args = {"transaction_id": transaction.id, "asset_code": asset.code}
        if callback:
            url_args["callback"] = callback
        if amount:
            url_args["amount"] = amount

        post_url = f"{reverse('post_interactive_deposit')}?{urlencode(url_args)}"
        get_url = f"{reverse('get_interactive_deposit')}?{urlencode(url_args)}"
        content.update(
            form=form,
            post_url=post_url,
            get_url=get_url,
            scripts=template_scripts,
            operation=settings.OPERATION_DEPOSIT,
            asset=asset,
            use_fee_endpoint=registered_fee_func != calculate_fee,
        )
        return Response(content,
                        template_name="polaris/deposit.html",
                        status=422)
Esempio n. 6
0
def get_interactive_deposit(request: Request) -> Response:
    """
    GET /transactions/deposit/webapp

    This endpoint retrieves the next form to be served to the user in the
    interactive flow. The following steps are taken during this process:

        1. URL arguments are parsed and validated.
        2. interactive_url() is called to determine whether or not the anchor
           uses an external service for the interactive flow. If a URL is
           returned, this function redirects to the URL. However, the session
           cookie should still be included in the response so future calls to
           GET /transactions/deposit/interactive/complete are authenticated.
        3. form_for_transaction() is called to retrieve the next form to
           render to the user.
        4. get and post URLs are constructed with the appropriate arguments
           and passed to the response to be rendered to the user.
    """
    args_or_error = interactive_args_validation(request)
    if "error" in args_or_error:
        return args_or_error["error"]

    transaction = args_or_error["transaction"]
    asset = args_or_error["asset"]
    callback = args_or_error["callback"]
    amount = args_or_error["amount"]

    url = rdi.interactive_url(request, transaction, asset, amount, callback)
    if url:  # The anchor uses a standalone interactive flow
        return redirect(url)

    form = rdi.form_for_transaction(transaction, amount=amount)
    content = rdi.content_for_template(Template.DEPOSIT,
                                       form=form,
                                       transaction=transaction)
    if not (form or content):
        logger.error(
            "The anchor did not provide content, unable to serve page.")
        if transaction.status != transaction.STATUS.incomplete:
            return render_error_response(
                _("The anchor did not provide content, is the interactive flow already complete?"
                  ),
                status_code=422,
                content_type="text/html",
            )
        return render_error_response(
            _("The anchor did not provide content, unable to serve page."),
            status_code=500,
            content_type="text/html",
        )
    elif content is None:
        content = {}

    if registered_scripts_func is not scripts:
        logger.warning(
            "DEPRECATED: the `scripts` Polaris integration function will be "
            "removed in Polaris 2.0 in favor of allowing the anchor to override "
            "and extend Polaris' Django templates. See the Template Extensions "
            "documentation for more information.")
    if form:
        template_scripts = registered_scripts_func({"form": form, **content})
    else:
        template_scripts = registered_scripts_func(content)

    url_args = {"transaction_id": transaction.id, "asset_code": asset.code}
    if callback:
        url_args["callback"] = callback
    if amount:
        url_args["amount"] = amount

    toml_data = registered_toml_func()
    post_url = f"{reverse('post_interactive_deposit')}?{urlencode(url_args)}"
    get_url = f"{reverse('get_interactive_deposit')}?{urlencode(url_args)}"
    content.update(
        form=form,
        post_url=post_url,
        get_url=get_url,
        scripts=template_scripts,
        operation=settings.OPERATION_DEPOSIT,
        asset=asset,
        use_fee_endpoint=registered_fee_func != calculate_fee,
        org_logo_url=toml_data.get("DOCUMENTATION", {}).get("ORG_LOGO"),
    )

    return Response(content, template_name="polaris/deposit.html")
Esempio n. 7
0
def post_interactive_withdraw(request: Request) -> Response:
    """
    POST /transactions/withdraw/webapp

    This endpoint processes form submissions during the withdraw interactive
    flow. The following steps are taken during this process:

        1. URL arguments are parsed and validated.
        2. form_for_transaction() is called to retrieve the form used to
           submit this request. This function is implemented by the anchor.
        3. The form is used to validate the data submitted, and if the form
           is a TransactionForm, the fee for the transaction is calculated.
        4. after_form_validation() is called to allow the anchor to process
           the data submitted. This function should change the application
           state such that the next call to form_for_transaction() returns
           the next form in the flow.
        5. form_for_transaction() is called again to retrieve the next
           form to be served to the user. If a form is returned, the
           function redirects to GET /transaction/withdraw/webapp. Otherwise,
           The user's session is invalidated, the transaction status is
           updated, and the function redirects to GET /more_info.
    """
    args_or_error = interactive_args_validation(request)
    if "error" in args_or_error:
        return args_or_error["error"]

    transaction = args_or_error["transaction"]
    asset = args_or_error["asset"]
    callback = args_or_error["callback"]
    amount = args_or_error["amount"]

    form = rwi.form_for_transaction(transaction, post_data=request.data)
    if not form:
        logger.error("Initial form_for_transaction() call returned None "
                     f"for {transaction.id}")
        if transaction.status != transaction.STATUS.incomplete:
            return render_error_response(
                _("The anchor did not provide content, is the interactive flow already complete?"
                  ),
                status_code=422,
                content_type="text/html",
            )
        return render_error_response(
            _("The anchor did not provide content, unable to serve page."),
            status_code=500,
            content_type="text/html",
        )

    if not form.is_bound:
        # The anchor must initialize the form with request.data
        logger.error(
            "form returned was not initialized with POST data, returning 500")
        return render_error_response(
            _("Unable to validate form submission."),
            status_code=500,
            content_type="text/html",
        )

    elif form.is_valid():
        if issubclass(form.__class__, TransactionForm):
            transaction.amount_in = form.cleaned_data["amount"]
            transaction.save()

        rwi.after_form_validation(form, transaction)
        next_form = rwi.form_for_transaction(transaction)
        if next_form or rwi.content_for_template(
                Template.WITHDRAW, form=next_form, transaction=transaction):
            args = {"transaction_id": transaction.id, "asset_code": asset.code}
            if amount:
                args["amount"] = amount
            if callback:
                args["callback"] = callback
            url = reverse("get_interactive_withdraw")
            return redirect(f"{url}?{urlencode(args)}")

        else:  # Last form has been submitted
            logger.info(
                f"Finished data collection and processing for transaction {transaction.id}"
            )
            invalidate_session(request)
            # Add memo now that interactive flow is complete
            #
            # We use the transaction ID as a memo on the Stellar transaction for the
            # payment in the withdrawal. This lets us identify that as uniquely
            # corresponding to this `Transaction` in the database. But a UUID4 is a 32
            # character hex string, while the Stellar HashMemo requires a 64 character
            # hex-encoded (32 byte) string. So, we zero-pad the ID to create an
            # appropriately sized string for the `HashMemo`.
            transaction_id_hex = transaction.id.hex
            padded_hex_memo = "0" * (
                64 - len(transaction_id_hex)) + transaction_id_hex
            transaction.memo = memo_hex_to_base64(padded_hex_memo)
            # Update status
            # This signals to the wallet that the transaction can be submitted
            transaction.status = Transaction.STATUS.pending_user_transfer_start
            transaction.save()
            url = reverse("more_info")
            args = urlencode({"id": transaction.id, "callback": callback})
            return redirect(f"{url}?{args}")

    else:
        content = (rwi.content_for_template(
            Template.WITHDRAW, form=form, transaction=transaction) or {})
        if registered_scripts_func is not scripts:
            logger.warning(
                "DEPRECATED: the `scripts` Polaris integration function will be "
                "removed in Polaris 2.0 in favor of allowing the anchor to override "
                "and extend Polaris' Django templates. See the Template Extensions "
                "documentation for more information.")
        template_scripts = registered_scripts_func({"form": form, **content})

        url_args = {"transaction_id": transaction.id, "asset_code": asset.code}
        if callback:
            url_args["callback"] = callback
        if amount:
            url_args["amount"] = amount

        toml_data = registered_toml_func()
        post_url = f"{reverse('post_interactive_withdraw')}?{urlencode(url_args)}"
        get_url = f"{reverse('get_interactive_withdraw')}?{urlencode(url_args)}"
        content.update(
            form=form,
            post_url=post_url,
            get_url=get_url,
            scripts=template_scripts,
            operation=settings.OPERATION_WITHDRAWAL,
            asset=asset,
            use_fee_endpoint=registered_fee_func != calculate_fee,
            org_logo_url=toml_data.get("DOCUMENTATION", {}).get("ORG_LOGO"),
        )
        return Response(content,
                        template_name="polaris/withdraw.html",
                        status=422)
Esempio n. 8
0
def get_interactive_withdraw(request: Request) -> Response:
    """
    GET /transactions/withdraw/webapp

    This endpoint retrieves the next form to be served to the user in the
    interactive flow. The following steps are taken during this process:

        1. URL arguments are parsed and validated.
        2. interactive_url() is called to determine whether or not the anchor
           uses an external service for the interactive flow. If a URL is
           returned, this function redirects to the URL. However, the session
           cookie should still be included in the response so future calls to
           GET /transactions/withdraw/interactive/complete are authenticated.
        3. form_for_transaction() is called to retrieve the next form to
           render to the user.
        4. get and post URLs are constructed with the appropriate arguments
           and passed to the response to be rendered to the user.
    """
    args_or_error = interactive_args_validation(request)
    if "error" in args_or_error:
        return args_or_error["error"]

    transaction = args_or_error["transaction"]
    asset = args_or_error["asset"]
    callback = args_or_error["callback"]
    amount = args_or_error["amount"]

    url = rwi.interactive_url(request, transaction, asset, amount, callback)
    if url:  # The anchor uses a standalone interactive flow
        return redirect(url)

    form = rwi.form_for_transaction(transaction, amount=amount)
    content = rwi.content_for_template(Template.WITHDRAW,
                                       form=form,
                                       transaction=transaction)
    if not (form or content):
        logger.error(
            "The anchor did not provide content, unable to serve page.")
        if transaction.status != transaction.STATUS.incomplete:
            return render_error_response(
                _("The anchor did not provide content, is the interactive flow already complete?"
                  ),
                status_code=422,
                content_type="text/html",
            )
        return render_error_response(
            _("The anchor did not provide content, unable to serve page."),
            status_code=500,
            content_type="text/html",
        )
    elif content is None:
        content = {}

    if form:
        scripts = registered_scripts_func({"form": form, **content})
    else:
        scripts = registered_scripts_func(content)

    url_args = {"transaction_id": transaction.id, "asset_code": asset.code}
    if callback:
        url_args["callback"] = callback
    if amount:
        url_args["amount"] = amount

    post_url = f"{reverse('post_interactive_withdraw')}?{urlencode(url_args)}"
    get_url = f"{reverse('get_interactive_withdraw')}?{urlencode(url_args)}"
    content.update(
        form=form,
        post_url=post_url,
        get_url=get_url,
        scripts=scripts,
        operation=settings.OPERATION_WITHDRAWAL,
        asset=asset,
        use_fee_endpoint=registered_fee_func != calculate_fee,
    )

    return Response(content, template_name="withdraw/form.html")