Source code for polaris.integrations.rails

from typing import List, Dict
from django.db.models import QuerySet

from polaris.models import Transaction


[docs]class RailsIntegration: """ A container class for functions that access off-chain rails, such banking accounts or other crypto networks. """ def poll_outgoing_transactions( self, transactions: QuerySet, *args: List, **kwargs: Dict ) -> List[Transaction]: """ Check the transactions that are still in a ``pending_external`` status and return the ones that are completed, meaning the user has received the funds. Polaris will update the transactions returned to ``Transaction.STATUS.completed``. `transactions` is passed as a Django ``QuerySet`` in case there are many pending transactions. You may want to query batches of ``Transaction`` objects to avoid consuming large amounts of memory. :param transactions: a ``QuerySet`` of ``Transaction`` objects """ raise NotImplementedError() def execute_outgoing_transaction( self, transaction: Transaction, *args: List, **kwargs: Dict ): """ Send the amount of the off-chain asset specified by `transaction` minus fees to the user associated with `transaction`. This function is used for SEP-6 & SEP-24 withdraws as well as SEP-31 payments. ``transaction.amount_fee`` and ``transaction.amount_out`` must be assigned in this function if they are not already. If the off-chain asset delivered to the user is different than the Stellar asset received on-chain, populate ``transaction.fee_asset`` as well. When this function is called, ``transaction.amount_in`` is the amount sent to the anchor, `not the amount specified in a SEP-24 or SEP-31 API call`. This matters because the amount actually sent to the anchor may differ from the amount specified in an API call. That is why you should always validate ``transaction.amount_in`` and recalculate ``transaction.amount_fee`` and ``transaction.amount_out`` here if necessary. If the amount is invalid in some way, the anchor must choose how to handle it. If you choose to refund the payment in its entirety, change ``transaction.status`` to `"error"`, assign an appropriate message to ``transaction.status_message``, and update `transaction.refunded` to ``True``. You could also refund a portion of the amount and continue processing the remaining amount. In this case, the ``transaction.status`` column should be assigned one of the expected statuses for this function, mentioned below, and the ``amount_in`` field should be reassigned the value the anchor accepted. If the funds transferred to the user become available in the user's off-chain account immediately, or the anchor cannot verify when funds have become available, update ``Transaction.status`` to ``Transaction.STATUS.completed``. If the transfer was simply initiated and is pending external systems, update the status to ``Transaction.STATUS.pending_external``. If an exception is raised, the transaction will be left in its current status and may be used again as a parameter to this function. To ensure the exception isn't repeatedly re-raised, change the problematic transaction's status to ``Transaction.STATUS.error``. If ``transaction.protocol == Transaction.PROTOCOL.sep31`` and more information is required from the sending anchor or user to complete the transaction, update the status to ``Transaction.STATUS.pending_transaction_info_update`` and save a JSON-serialized string containing the fields that need updating to the ``Transaction.required_info_update`` column. The JSON string should be in the format returned from ``SEP31ReceiverIntegration.info()``. You can also optionally save a human-readable message to ``Transaction.required_info_message``. Both fields will be included in the `/transaction` response requested by the sending anchor. If the SEP-31 transaction is waiting for an update, the sending anchor will eventually make a request to the `PATCH /transaction` endpoint with the information specified in ``Transaction.required_info_update``. Once updated, this function will be called again with the updated transaction. :param transaction: the ``Transaction`` object associated with the payment this function should make """ raise NotImplementedError() def poll_pending_deposits( self, pending_deposits: QuerySet, *args: List, **kwargs: Dict ) -> List[Transaction]: """ .. _autocommit: https://docs.djangoproject.com/en/2.2/topics/db/transactions/#autocommit This function should poll the appropriate financial entity for the state of all `pending_deposits` and return the ones that have externally completed, meaning the off-chain funds are available in the anchor's account. For each returned transaction, ``Transaction.amount_fee`` and ``Transaction.amount_out`` must be assigned. If the off-chain asset collected from the user is different than the Stellar asset to be sent on-chain, populate ``transaction.fee_asset`` as well. Client applications may send an amount that differs from the amount originally specified prior. If ``amount_expected`` differs from the amount deposited, the transaction should be placed in the ``error`` status or assign the amount deposited to ``amount_in`` and update ``amount_fee`` to appropriately. Also make sure to save the transaction's ``from_address`` field with the account the funds originated from. Any changes to the a transaction object must be saved to the database before it is returned. The transaction object will be refreshed using Django's ``.refresh_from_db()`` method and any unsaved data will be lost. For every transaction that is returned, Polaris will evaluate its readiness for submission to the Stellar network. If a transaction is completed on the network, the ``after_deposit()`` integration function will be called, however implementing this function is optional. `pending_deposits` is a QuerySet of the form :: pending_deposits = Transactions.object.filter( kind=Transaction.KIND.deposit, status=[ Transaction.STATUS.pending_user_transfer_start, Transaction.STATUS.pending_external ], pending_execution_attempt=False ) ``pending_user_transfer_start`` is the proper status for a transaction when the user must take some action to proceed. In this case, that action is sending the deposit funds. ``pending_external`` is the proper status for a transaction when the deposit funds have been sent but have not arrived in the anchor's off-chain account. If the anchor cannot detect when deposit funds are sent but not received, it is perfectly acceptable to keep the transaction in ``pending_user_transfer_start`` until the funds have arrived. **Note**: As of verison 1.3, this function is called within a database transaction context manager: :: with django.db.transaction.atomic(): ready_transactions = rri.poll_pending_deposits( pending_deposits.select_for_update() ) Transaction.objects.filter( id__in=[t.id for t in ready_transactions] ).update(pending_execution_attempt=True) This is done to ensure the same ``Transaction`` object is not retrieved from the database by multiple invocations of the process_pending_deposits command and submitted to Stellar as unique transactions. This differs from the majority of other queries Polaris makes, which are executed in autocommit_ mode, the Django default. :param pending_deposits: a django Queryset for pending Transactions :return: a list of ``Transaction`` objects which correspond to successful user deposits to the anchor's account. """ raise NotImplementedError()
registered_rails_integration = RailsIntegration()