def withdraw(account: str, request: Request) -> Response: """ `POST /transactions/withdraw` initiates the withdrawal and returns an interactive withdrawal form to the user. """ asset_code = request.POST.get("asset_code") if not asset_code: return render_error_response("'asset_code' is required") # TODO: Verify optional arguments. # Verify that the asset code exists in our database, with withdraw enabled. asset = Asset.objects.filter(code=asset_code).first() if not asset or not asset.withdrawal_enabled: return render_error_response( f"invalid operation for asset {asset_code}") # 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 = create_transaction_id() transaction_id_hex = transaction_id.hex withdraw_memo = "0" * (64 - len(transaction_id_hex)) + transaction_id_hex Transaction.objects.create( id=transaction_id, stellar_account=account, asset=asset, kind=Transaction.KIND.withdrawal, status=Transaction.STATUS.incomplete, withdraw_anchor_account=settings.STELLAR_DISTRIBUTION_ACCOUNT_ADDRESS, withdraw_memo=withdraw_memo, withdraw_memo_type=Transaction.MEMO_TYPES.hash, ) url = rwi.interactive_url(request, str(transaction_id), account, asset_code) return Response({ "type": "interactive_customer_info_needed", "url": url, "id": transaction_id })
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 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 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, ) return Response(content, template_name="polaris/withdraw.html")
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. 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 = rwi.interactive_url(request, transaction, asset, amount, callback) if url: # The anchor uses a standalone interactive flow return redirect(url) content = rwi.content_for_transaction(transaction) if not content: logger.error("The anchor did not provide a form, unable to serve page.") return render_error_response( _("The anchor did not provide a form, unable to serve page."), status_code=500, content_type="text/html", ) if content.get("form"): form_class = content.pop("form") if issubclass(form_class, TransactionForm) and amount: content["form"] = form_class({"amount": amount}) else: content["form"] = form_class() 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( post_url=post_url, get_url=get_url, scripts=registered_javascript_func() ) return Response(content, template_name="withdraw/form.html")