def _save_transaction(self, bucket=None):
        """Save this transaction to the object store

           Args:
                bucket (dict): Bucket to load data from
           Returns:
                None
        """
        from Acquire.Accounting import Ledger as _Ledger
        _Ledger.save_transaction(self, bucket=bucket)
    def _load_transaction(self, uid, bucket=None):
        """Load this transaction from the object store

           Args:
                uid (str): UID of transaction to load
                bucket (dict): Bucket to load data from
           Returns:
                None
        """
        from Acquire.Accounting import Ledger as _Ledger
        self.__dict__ = _copy(_Ledger.load_transaction(
                                        uid, bucket=bucket).__dict__)
예제 #3
0
    def perform_transaction(key, result):
        delta1 = zero
        delta2 = zero

        # need to work with thread-local copies of the accounts
        my_account1 = Account(uid=account1.uid())
        my_account2 = Account(uid=account2.uid())

        for i in range(0, 10):
            transaction = Transaction(value=create_decimal(random.random()),
                                      description="Transaction %d" % i)

            if random.randint(0, 1):
                auth = Authorisation(
                    resource=transaction.fingerprint(),
                    testing_key=testing_key,
                    testing_user_guid=my_account1.group_name())

                Ledger.perform(transaction=transaction,
                               debit_account=my_account1,
                               credit_account=my_account2,
                               authorisation=auth)
                delta1 -= transaction.value()
                delta2 += transaction.value()
            else:
                auth = Authorisation(
                    resource=transaction.fingerprint(),
                    testing_key=testing_key,
                    testing_user_guid=my_account2.group_name())

                Ledger.perform(transaction=transaction,
                               debit_account=my_account2,
                               credit_account=my_account1,
                               authorisation=auth)
                delta1 += transaction.value()
                delta2 -= transaction.value()

        with rlock:
            result[key] = (delta1, delta2)
예제 #4
0
    def perform_transaction(key, result):
        delta1 = zero
        delta2 = zero
        auth = Authorisation()

        # need to work with thread-local copies of the accounts
        my_account1 = Account(uid=account1.uid())
        my_account2 = Account(uid=account2.uid())

        for i in range(0, 5):
            transaction = Transaction(value=create_decimal(random.random()),
                                      description="Transaction %d" % i)

            if random.randint(0, 1):
                Ledger.perform(transaction, my_account1, my_account2, auth)
                delta1 -= transaction.value()
                delta2 += transaction.value()
            else:
                Ledger.perform(transaction, my_account2, my_account1, auth)
                delta1 += transaction.value()
                delta2 -= transaction.value()

        with rlock:
            result[key] = (delta1, delta2)
    def load_test_and_set(uid, expected_state, new_state,
                          bucket=None):
        """Static method to load up the Transaction record associated with
           the passed UID, check that the transaction state matches
           'expected_state', and if it does, to update the transaction
           state to 'new_state'. This returns the loaded (and updated)
           transaction

           Args:
                expected_state (TransactionState): State of transaction
                new_state (TransactionState): State to update transaction to
                bucket (dict): Bucket to load data from

           Returns:
                Transaction: Updated transaction
        """
        if bucket is None:
            from Acquire.Service import get_service_account_bucket \
                as _get_service_account_bucket
            bucket = _get_service_account_bucket()

        from Acquire.Accounting import Ledger as _Ledger
        from Acquire.ObjectStore import Mutex as _Mutex

        try:
            mutex = _Mutex(uid, timeout=600, lease_time=600)
        except Exception as e:
            raise LedgerError("Cannot secure a Ledger mutex for transaction "
                              "'%s'. Error = %s" % (uid, str(e)))

        try:
            transaction = _Ledger.load_transaction(uid, bucket)

            if transaction.transaction_state() != expected_state:
                raise TransactionError(
                    "Cannot update the state of the transaction %s from "
                    "%s to %s as it is not in the expected state" %
                    (str(transaction), expected_state.value, new_state.value))

            transaction._transaction_state = new_state
        except:
            mutex.unlock()
            raise

        # now need to write anything back if the state isn't changed
        if expected_state == new_state:
            return transaction

        # make sure we have enough time remaining on the lease to be
        # able to write this result back to the object store...
        if mutex.seconds_remaining_on_lease() < 100:
            try:
                mutex.fully_unlock()
            except:
                pass

            return TransactionRecord.load_test_and_set(uid, expected_state,
                                                       new_state, bucket)

        try:
            _Ledger.save_transaction(transaction, bucket)
        except:
            mutex.unlock()
            raise

        return transaction
예제 #6
0
def run(args):
    """This function is called to handle request to cash cheques. This
       will verify that the cheque is valid and will then create
       the debit/credit note pair for the transation. It will return
       the CreditNote to the caller so they can see that the funds have
       been reserved, and can receipt the transaction once goods/services
       have been delivered.

       Args:
            args (dict): information for payment for service

        Returns:
            dict: contains status, status message and credit note if valid

    """

    credit_notes = []

    try:
        cheque = args["cheque"]
    except:
        raise ValueError("You must supply a cheque to be cashed!")

    try:
        cheque = Cheque.from_data(cheque)
    except Exception as e:
        from Acquire.Service import exception_to_string
        raise TypeError("Unable to interpret the cheque.\n\nCAUSE: %s" %
                        exception_to_string(e))

    try:
        spend = args["spend"]
    except:
        spend = None

    if spend is not None:
        try:
            spend = string_to_decimal(spend)
        except Exception as e:
            from Acquire.Service import exception_to_string
            raise TypeError("Unable to interpret the spend.\n\nCause: %s" %
                            exception_to_string(e))

    try:
        resource = str(args["resource"])
    except:
        raise ValueError(
            "You must supply a string representing the resource that will "
            "be paid for using this cheque")

    try:
        account_uid = str(args["account_uid"])
    except:
        raise ValueError("You must supply the UID of the account to which the "
                         "cheque will be cashed")

    try:
        receipt_by = args["receipt_by"]
    except:
        raise ValueError(
            "You must supply the datetime by which you promise to "
            "receipt this transaction")

    try:
        receipt_by = string_to_datetime(receipt_by)
    except Exception as e:
        from Acquire.Service import exception_to_string
        raise TypeError(
            "Unable to interpret the receipt_by date.\n\nCAUSE: %s" %
            exception_to_string(e))

    # now read the cheque - this will only succeed if the cheque
    # is valid, has been signed, has been sent from the right
    # service, and was authorised by the user, the cheque
    # has not expired and we are the
    # service which holds the account from which funds are drawn
    info = cheque.read(resource=resource, spend=spend, receipt_by=receipt_by)

    try:
        description = str(args["description"])
    except:
        description = info["resource"]

    authorisation = info["authorisation"]
    auth_resource = info["auth_resource"]
    user_guid = authorisation.user_guid()

    # the cheque is valid
    bucket = get_service_account_bucket()

    try:
        debit_account = Account(uid=info["account_uid"], bucket=bucket)
    except Exception as e:
        from Acquire.Service import exception_to_string
        raise PaymentError("Cannot find the account associated with the cheque"
                           "\n\nCAUSE: %s" % exception_to_string(e))

    try:
        credit_account = Account(uid=account_uid, bucket=bucket)
    except Exception as e:
        from Acquire.Service import exception_to_string
        raise PaymentError(
            "Cannot find the account to which funds will be creditted:"
            "\n\nCAUSE: %s" % exception_to_string(e))

    # validate that this account is in a group that can be authorised
    # by the user (this should eventually go as the ACLs now allow users
    # to authorised payments from many accounts)
    accounts = Accounts(user_guid=user_guid)
    if not accounts.contains(account=debit_account, bucket=bucket):
        raise PermissionError(
            "The user with UID '%s' cannot authorise transactions from "
            "the account '%s' as they do not own this account." %
            (user_guid, str(debit_account)))

    transaction = Transaction(value=info["spend"], description=description)

    # we have enough information to perform the transaction
    # - this is provisional as the service must receipt everything
    transaction_records = Ledger.perform(transactions=transaction,
                                         debit_account=debit_account,
                                         credit_account=credit_account,
                                         authorisation=authorisation,
                                         authorisation_resource=auth_resource,
                                         is_provisional=True,
                                         receipt_by=receipt_by,
                                         bucket=bucket)

    # extract all of the credit notes to return to the user,
    # and also to record so that we can check if they have not
    # been receipted in time...
    credit_notes = []

    for record in transaction_records:
        credit_notes.append(record.credit_note())

    credit_notes = list_to_string(credit_notes)

    receipt_key = "accounting/cashed_cheque/%s" % info["uid"]
    mutex = Mutex(receipt_key, bucket=bucket)

    try:
        receipted = ObjectStore.get_object_from_json(bucket, receipt_key)
    except:
        receipted = None

    if receipted is not None:
        # we have tried to cash this cheque twice!
        mutex.unlock()
        Ledger.refund(transaction_records, bucket=bucket)
    else:
        info = {"status": "needs_receipt", "creditnotes": credit_notes}
        ObjectStore.set_object_from_json(bucket, receipt_key, info)
        mutex.unlock()

    return {"credit_notes": credit_notes}
예제 #7
0
def test_temporal_transactions(account1, account2, bucket):
    if not have_freezetime:
        return

    zero = create_decimal(0)

    balance1 = zero
    balance2 = zero
    final_balance1 = zero
    final_balance2 = zero
    liability1 = zero
    liability2 = zero
    receivable1 = zero
    receivable2 = zero

    # generate some random times for the transactions
    random_dates = []
    now = get_datetime_now()
    for i in range(0, 50):
        if i == 0:
            # this is an evil edge-case datetime
            s = "2019-01-20 20:59:59.092627+00:00"
            r = datetime.datetime.fromisoformat(s)
        else:
            r = start_time + random.random() * (now - start_time)

        while (r.minute == 59 and r.second >= 58) or \
              (r.minute == 0 and r.second == 0 and r.microsecond < 10):
            r = r + datetime.timedelta(seconds=1)

        random_dates.append(r)

    random_dates.sort()

    # (which must be applied in time order!)
    random_dates.sort()

    provisionals = []

    for (i, transaction_time) in enumerate(random_dates):
        with freeze_time(transaction_time) as _frozen_datetime:
            now = get_datetime_now()
            assert (transaction_time == now)

            is_provisional = (random.randint(0, 3) <= 2)

            # check search for transaction is not O(n^2) lookup scanning
            # through the keys...

            transaction = Transaction(25 * random.random(),
                                      "test transaction %d" % i)

            if random.randint(0, 1):
                debit_account = account1
                credit_account = account2

                if is_provisional:
                    liability1 += transaction.value()
                    receivable2 += transaction.value()
                else:
                    balance1 -= transaction.value()
                    balance2 += transaction.value()

                final_balance1 -= transaction.value()
                final_balance2 += transaction.value()
            else:
                debit_account = account2
                credit_account = account1

                if is_provisional:
                    receivable1 += transaction.value()
                    liability2 += transaction.value()
                else:
                    balance1 += transaction.value()
                    balance2 -= transaction.value()

                final_balance1 += transaction.value()
                final_balance2 -= transaction.value()

            auth = Authorisation(resource=transaction.fingerprint(),
                                 testing_key=testing_key,
                                 testing_user_guid=debit_account.group_name())

            records = Ledger.perform(transaction=transaction,
                                     debit_account=debit_account,
                                     credit_account=credit_account,
                                     authorisation=auth,
                                     is_provisional=is_provisional,
                                     bucket=bucket)

            for record in records:
                assert (record.datetime() == now)

            if is_provisional:
                for record in records:
                    provisionals.append((credit_account, record))
            elif (random.randint(0, 3) <= 2):
                # receipt pending transactions
                balance1 = Balance(balance=balance1,
                                   liability=liability1,
                                   receivable=receivable1)

                balance2 = Balance(balance=balance2,
                                   liability=liability2,
                                   receivable=receivable2)

                assert (account1.balance() == balance1)
                assert (account2.balance() == balance2)

                for (credit_account, record) in provisionals:
                    credit_note = record.credit_note()
                    auth = Authorisation(
                        resource=credit_note.fingerprint(),
                        testing_key=testing_key,
                        testing_user_guid=credit_account.group_name())

                    receipted_value = create_decimal(
                        random.random() * float(credit_note.value()))

                    delta_value = credit_note.value() - receipted_value

                    Ledger.receipt(Receipt(credit_note=credit_note,
                                           receipted_value=receipted_value,
                                           authorisation=auth),
                                   bucket=bucket)

                    if credit_note.debit_account_uid() == account1.uid():
                        final_balance1 += delta_value
                        final_balance2 -= delta_value
                    else:
                        final_balance2 += delta_value
                        final_balance1 -= delta_value

                assert (account1.balance() == Balance(balance=final_balance1))
                assert (account2.balance() == Balance(balance=final_balance2))

                provisionals = []
                balance1 = final_balance1
                balance2 = final_balance2
                liability1 = zero
                liability2 = zero
                receivable1 = zero
                receivable2 = zero

    balance1 = Balance(balance=balance1,
                       liability=liability1,
                       receivable=receivable1)

    balance2 = Balance(balance=balance2,
                       liability=liability2,
                       receivable=receivable2)

    assert (account1.balance() == balance1)
    assert (account2.balance() == balance2)

    for (credit_account, record) in provisionals:
        credit_note = record.credit_note()
        auth = Authorisation(resource=record.credit_note().fingerprint(),
                             testing_key=testing_key,
                             testing_user_guid=credit_account.group_name())

        receipted_value = create_decimal(random.random() *
                                         float(credit_note.value()))

        delta_value = credit_note.value() - receipted_value

        Ledger.receipt(Receipt(credit_note=credit_note,
                               authorisation=auth,
                               receipted_value=receipted_value),
                       bucket=bucket)

        if credit_note.debit_account_uid() == account1.uid():
            final_balance1 += delta_value
            final_balance2 -= delta_value
        else:
            final_balance2 += delta_value
            final_balance1 -= delta_value

    assert (account1.balance() == Balance(balance=final_balance1))
    assert (account2.balance() == Balance(balance=final_balance2))
예제 #8
0
def run(args):
    """This function is called to handle requests from a user to deposit
       more funds into their account. This will add this deposit as a
       debt for the user. Once the debt exceeds a certain value, then the
       backend-payment system will charge the user's real account to
       recover the funds

       Args:
            args (dict): data for deposit of funds into the account

        Returns:
            dict: contains status, status message and details regarding
                the deposit into the account and invoice data
    """

    transaction_records = None
    invoice_value = None
    invoice_user = None

    try:
        authorisation = Authorisation.from_data(args["authorisation"])
    except:
        authorisation = None

    transaction = Transaction.from_data(args["transaction"])

    if authorisation is None:
        raise PermissionError("You must supply a valid authorisation "
                              "to deposit funds into your account")

    if transaction is None or transaction.is_null():
        raise ValueError("You must supply a valid transaction that "
                         "represents the deposit")

    try:
        account_name = str(args["account_name"])
    except:
        account_name = "deposits"

    if transaction.value() > 0:
        user_guid = authorisation.user_guid()

        # load the account from which the transaction will be performed
        bucket = get_service_account_bucket()
        accounts = Accounts(user_guid=user_guid)

        # deposits are made by transferring funds from the user's
        # 'billing' account to their named account (or 'deposits' account)
        deposit_account = accounts.create_account(account_name,
                                                  "Deposit account",
                                                  bucket=bucket)

        billing_account = accounts.create_account("billing",
                                                  "Billing account",
                                                  overdraft_limit=150,
                                                  bucket=bucket)

        billing_balance = billing_account.balance() - transaction.value()

        if billing_balance.balance() < -50.0:
            # there are sufficient funds that need to be transferred that
            # it is worth really charging the user
            invoice_user = user_guid
            invoice_value = billing_balance

        # we have enough information to perform the transaction
        transaction_records = Ledger.perform(transactions=transaction,
                                             debit_account=billing_account,
                                             credit_account=deposit_account,
                                             authorisation=authorisation,
                                             is_provisional=False,
                                             bucket=bucket)

    return_value = {}

    if transaction_records:
        try:
            transaction_records[0]
        except:
            transaction_records = [transaction_records]

        for i in range(0, len(transaction_records)):
            transaction_records[i] = transaction_records[i].to_data()

        return_value["transaction_records"] = transaction_records

    if invoice_user:
        return_value["invoice_user"] = invoice_user
        return_value["invoice_value"] = str(invoice_value)

    return return_value
예제 #9
0
def test_pending_transactions(random_transaction):
    (transaction, account1, account2) = random_transaction

    starting_balance1 = account1.balance()
    starting_balance2 = account2.balance()

    authorisation = Authorisation(resource=transaction.fingerprint(),
                                  testing_key=testing_key,
                                  testing_user_guid=account1.group_name())

    records = Ledger.perform(transactions=transaction,
                             debit_account=account1,
                             credit_account=account2,
                             authorisation=authorisation,
                             is_provisional=True)

    assert (len(records) == 1)
    record = records[0]

    ending_balance1 = account1.balance()
    ending_balance2 = account2.balance()

    assert (ending_balance1.liability() == starting_balance1.liability() +
            transaction.value())
    assert (ending_balance2.receivable() == starting_balance2.receivable() +
            transaction.value())
    assert (starting_balance1.balance() == ending_balance1.balance())
    assert (starting_balance2.balance() == ending_balance2.balance())
    assert (starting_balance2.liability() == ending_balance2.liability())
    assert (starting_balance1.receivable() == ending_balance1.receivable())

    assert (record.debit_account_uid() == account1.uid())
    assert (record.credit_account_uid() == account2.uid())

    debit_note = record.debit_note()
    credit_note = record.credit_note()

    assert (not debit_note.is_null())
    assert (not credit_note.is_null())

    assert (debit_note.account_uid() == account1.uid())
    assert (credit_note.account_uid() == account2.uid())

    assert (debit_note.is_provisional())
    assert (credit_note.is_provisional())

    assert (debit_note.value() == transaction.value())
    assert (credit_note.value() == transaction.value())

    now = get_datetime_now()

    assert (debit_note.datetime() < now)
    assert (credit_note.datetime() < now)
    assert (debit_note.datetime() <= credit_note.datetime())

    assert_packable(debit_note)
    assert_packable(credit_note)

    # now receipt a random amount of the transaction
    authorisation = Authorisation(resource=credit_note.fingerprint(),
                                  testing_key=testing_key,
                                  testing_user_guid=account2.group_name())

    with pytest.raises(ValueError):
        receipt = Receipt(
            credit_note, authorisation,
            create_decimal(random.random()) + credit_note.value())

    if random.randint(0, 1):
        value = credit_note.value()
        receipt = Receipt(credit_note, authorisation)
    else:
        value = create_decimal(
            create_decimal(random.random()) * credit_note.value())
        receipt = Receipt(credit_note, authorisation, value)

    assert (not receipt.is_null())
    assert (receipt.authorisation() == authorisation)
    assert (receipt.receipted_value() == value)
    assert (receipt.credit_note() == credit_note)
    assert_packable(receipt)

    rrecords = Ledger.receipt(receipt)

    assert (len(rrecords) == 1)
    rrecord = rrecords[0]

    assert (not rrecord.is_null())
    assert_packable(rrecord)

    assert (not rrecord.is_provisional())
    assert (rrecord.is_direct())
    assert (rrecord.get_receipt_info() == receipt)
    assert (rrecord.is_receipt())
    assert (rrecord.original_transaction() == transaction)

    # the original transaction record has now been updated to
    # say that it has been receipted...
    assert (record.is_provisional())
    record.reload()
    assert (record.is_receipted())

    assert (rrecord.original_transaction_record() == record)

    ending_balance1 = account1.balance()
    ending_balance2 = account2.balance()

    assert (ending_balance1.liability() == starting_balance1.liability())
    assert (ending_balance2.receivable() == starting_balance2.receivable())
    assert (starting_balance1.balance() - value == ending_balance1.balance())
    assert (starting_balance2.balance() + value == ending_balance2.balance())
    assert (starting_balance2.liability() == ending_balance2.liability())
    assert (starting_balance1.receivable() == ending_balance1.receivable())
예제 #10
0
def test_transactions(random_transaction, bucket):

    (transaction, account1, account2) = random_transaction

    starting_balance1 = account1.balance()

    starting_balance2 = account2.balance()

    authorisation = Authorisation(resource=transaction.fingerprint(),
                                  testing_key=testing_key,
                                  testing_user_guid=account1.group_name())

    records = Ledger.perform(transaction=transaction,
                             debit_account=account1,
                             credit_account=account2,
                             authorisation=authorisation,
                             is_provisional=False,
                             bucket=bucket)

    assert (len(records) == 1)

    record = records[0]

    ending_balance1 = account1.balance()
    ending_balance2 = account2.balance()

    assert (ending_balance1 == starting_balance1 - transaction)
    assert (ending_balance2 == starting_balance2 + transaction)

    assert (record.debit_account_uid() == account1.uid())
    assert (record.credit_account_uid() == account2.uid())

    debit_note = record.debit_note()
    credit_note = record.credit_note()

    assert (debit_note.account_uid() == account1.uid())
    assert (credit_note.account_uid() == account2.uid())

    assert (not debit_note.is_provisional())
    assert (not credit_note.is_provisional())

    assert (debit_note.value() == transaction.value())
    assert (credit_note.value() == transaction.value())

    now = get_datetime_now()

    assert (debit_note.datetime() < now)
    assert (credit_note.datetime() < now)
    assert (debit_note.datetime() <= credit_note.datetime())

    assert_packable(debit_note)
    assert_packable(credit_note)

    # now test refunding this transaction
    # now receipt a random amount of the transaction
    authorisation = Authorisation(resource=credit_note.fingerprint(),
                                  testing_key=testing_key,
                                  testing_user_guid=account2.group_name())

    refund = Refund(credit_note, authorisation)

    assert (not refund.is_null())
    assert (refund.authorisation() == authorisation)
    assert (refund.value() == transaction.value())
    assert (refund.credit_note() == credit_note)
    assert_packable(refund)

    rrecords = Ledger.refund(refund)

    assert (len(rrecords) == 1)
    rrecord = rrecords[0]

    assert (not rrecord.is_null())
    assert_packable(rrecord)

    assert (not rrecord.is_provisional())
    assert (rrecord.is_direct())
    assert (rrecord.get_refund_info() == refund)
    assert (rrecord.is_refund())
    assert (rrecord.original_transaction() == transaction)

    # the original transaction record has now been updated to
    # say that it has been receipted...
    assert (record.is_direct())
    record.reload()
    assert (record.is_refunded())

    assert (rrecord.original_transaction_record() == record)

    ending_balance1 = account1.balance()
    ending_balance2 = account2.balance()

    assert (ending_balance1.liability() == starting_balance1.liability())
    assert (ending_balance2.receivable() == starting_balance2.receivable())
    assert (starting_balance1.balance() == ending_balance1.balance())
    assert (starting_balance2.balance() == ending_balance2.balance())
    assert (starting_balance2.liability() == ending_balance2.liability())
    assert (starting_balance1.receivable() == ending_balance1.receivable())
예제 #11
0
def test_temporal_transactions(account1, account2, bucket):
    if not have_freezetime:
        return

    zero = create_decimal(0)

    balance1 = zero
    balance2 = zero
    final_balance1 = zero
    final_balance2 = zero
    liability1 = zero
    liability2 = zero
    receivable1 = zero
    receivable2 = zero

    # generate some random times for the transactions
    random_dates = []
    now = datetime.datetime.now()
    for i in range(0, 100):
        random_dates.append(start_time + random.random() * (now - start_time))

    # (which must be applied in time order!)
    random_dates.sort()

    records = []

    for (i, transaction_time) in enumerate(random_dates):
        with freeze_time(transaction_time) as frozen_datetime:
            now = datetime.datetime.now()
            assert (frozen_datetime() == now)

            is_provisional = random.randint(0, 5)

            transaction = Transaction(25 * random.random(),
                                      "test transaction %d" % i)
            auth = Authorisation()

            if random.randint(0, 10):
                record = Ledger.perform(transaction,
                                        account1,
                                        account2,
                                        auth,
                                        is_provisional,
                                        bucket=bucket)

                if is_provisional:
                    liability1 += transaction.value()
                    receivable2 += transaction.value()
                else:
                    balance1 -= transaction.value()
                    balance2 += transaction.value()

                final_balance1 -= transaction.value()
                final_balance2 += transaction.value()
            else:
                record = Ledger.perform(transaction,
                                        account2,
                                        account1,
                                        auth,
                                        is_provisional,
                                        bucket=bucket)

                if is_provisional:
                    receivable1 += transaction.value()
                    liability2 += transaction.value()
                else:
                    balance1 += transaction.value()
                    balance2 -= transaction.value()

                final_balance1 += transaction.value()
                final_balance2 -= transaction.value()

            if is_provisional:
                records.append(record)

            assert (record.timestamp() == now.timestamp())

    assert (account1.balance() == balance1)
    assert (account2.balance() == balance2)
    assert (account1.liability() == liability1)
    assert (account1.receivable() == receivable1)
    assert (account2.liability() == liability2)
    assert (account2.receivable() == receivable2)

    for record in records:
        Ledger.receipt(Receipt(record.credit_note(), Authorisation()),
                       bucket=bucket)

    assert (account1.balance() == final_balance1)
    assert (account2.balance() == final_balance2)

    assert (account1.liability() == zero)
    assert (account1.receivable() == zero)
    assert (account2.liability() == zero)
    assert (account2.receivable() == zero)
예제 #12
0
def run(args):
    """This function is called to handle requests to perform transactions
       between accounts

       Args:
            args (dict): data for account transfers

        Returns:
            dict: contains status, status message and transaction
            records if any are available
    """

    transaction_records = None

    try:
        debit_account_uid = str(args["debit_account_uid"])
    except:
        debit_account_uid = None

    try:
        credit_account_uid = str(args["credit_account_uid"])
    except:
        credit_account_uid = None

    try:
        authorisation = Authorisation.from_data(args["authorisation"])
    except:
        authorisation = None

    try:
        transaction = Transaction.from_data(args["transaction"])
    except:
        transaction = None

    try:
        is_provisional = bool(args["is_provisional"])
    except:
        is_provisional = None

    if debit_account_uid is None:
        raise TransactionError("You must supply the account UID "
                               "for the debit account")

    if credit_account_uid is None:
        raise TransactionError("You must supply the account UID "
                               "for the credit account")

    if debit_account_uid == credit_account_uid:
        raise TransactionError(
            "You cannot perform a transaction where the debit and credit "
            "accounts are the same!")

    if transaction is None or transaction.is_null():
        raise TransactionError("You must supply a valid transaction to "
                               "perform!")

    if is_provisional is None:
        raise TransactionError("You must say whether or not the "
                               "transaction is provisional using "
                               "is_provisional")

    if authorisation is None:
        raise PermissionError("You must supply a valid authorisation "
                              "to perform transactions between accounts")

    authorisation.assert_once()
    user_guid = authorisation.user_guid()

    # load the account from which the transaction will be performed
    bucket = get_service_account_bucket()
    debit_account = Account(uid=debit_account_uid, bucket=bucket)

    # validate that this account is in a group that can be authorised
    # by the user - This should eventually go as this is all
    # handled by the ACLs
    if not Accounts(user_guid).contains(account=debit_account,
                                        bucket=bucket):
        raise PermissionError(
            "The user with GUID '%s' cannot authorise transactions from "
            "the account '%s' as they do not own this account." %
            (user_guid, str(debit_account)))

    # now load the two accounts involved in the transaction
    credit_account = Account(uid=credit_account_uid, bucket=bucket)

    # we have enough information to perform the transaction
    transaction_records = Ledger.perform(transactions=transaction,
                                         debit_account=debit_account,
                                         credit_account=credit_account,
                                         authorisation=authorisation,
                                         is_provisional=is_provisional,
                                         bucket=bucket)

    return_value = {}

    if transaction_records:
        try:
            transaction_records[0]
        except:
            transaction_records = [transaction_records]

        for i in range(0, len(transaction_records)):
            transaction_records[i] = transaction_records[i].to_data()

        return_value["transaction_records"] = transaction_records

    return return_value
예제 #13
0
def test_pending_transactions(random_transaction):
    (transaction, account1, account2) = random_transaction

    starting_balance1 = account1.balance()
    starting_liability1 = account1.liability()
    starting_receivable1 = account1.receivable()

    starting_balance2 = account2.balance()
    starting_liability2 = account2.liability()
    starting_receivable2 = account2.receivable()

    record = Ledger.perform(transaction,
                            account1,
                            account2,
                            Authorisation(),
                            is_provisional=True)

    ending_balance1 = account1.balance()
    ending_liability1 = account1.liability()
    ending_receivable1 = account1.receivable()

    ending_balance2 = account2.balance()
    ending_liability2 = account2.liability()
    ending_receivable2 = account2.receivable()

    assert (ending_liability1 == starting_liability1 + transaction.value())
    assert (ending_receivable2 == starting_receivable2 + transaction.value())
    assert (starting_balance1 == ending_balance1)
    assert (starting_balance2 == ending_balance2)
    assert (starting_liability2 == ending_liability2)
    assert (starting_receivable1 == ending_receivable1)

    assert (record.debit_account_uid() == account1.uid())
    assert (record.credit_account_uid() == account2.uid())

    debit_note = record.debit_note()
    credit_note = record.credit_note()

    assert (not debit_note.is_null())
    assert (not credit_note.is_null())

    assert (debit_note.account_uid() == account1.uid())
    assert (credit_note.account_uid() == account2.uid())

    assert (debit_note.is_provisional())
    assert (credit_note.is_provisional())

    assert (debit_note.value() == transaction.value())
    assert (credit_note.value() == transaction.value())

    now = datetime.datetime.now()

    assert (debit_note.timestamp() < now.timestamp())
    assert (credit_note.timestamp() < now.timestamp())
    assert (debit_note.timestamp() <= credit_note.timestamp())

    assert_packable(debit_note)
    assert_packable(credit_note)

    # now receipt a random amount of the transaction
    auth = Authorisation()

    with pytest.raises(ValueError):
        receipt = Receipt(
            credit_note, auth,
            create_decimal(random.random()) + credit_note.value())

    if random.randint(0, 1):
        value = credit_note.value()
        receipt = Receipt(credit_note, auth)
    else:
        value = create_decimal(
            create_decimal(random.random()) * credit_note.value())
        receipt = Receipt(credit_note, auth, value)

    assert (not receipt.is_null())
    assert (receipt.authorisation() == auth)
    assert (receipt.receipted_value() == value)
    assert (receipt.credit_note() == credit_note)
    assert_packable(receipt)

    rrecord = Ledger.receipt(receipt)

    assert (not rrecord.is_null())
    assert_packable(rrecord)

    assert (not rrecord.is_provisional())
    assert (rrecord.is_direct())
    assert (rrecord.get_receipt_info() == receipt)
    assert (rrecord.is_receipt())
    assert (rrecord.original_transaction() == transaction)

    # the original transaction record has now been updated to
    # say that it has been receipted...
    assert (record.is_provisional())
    record.reload()
    assert (record.is_receipted())

    assert (rrecord.original_transaction_record() == record)

    ending_balance1 = account1.balance()
    ending_liability1 = account1.liability()
    ending_receivable1 = account1.receivable()

    ending_balance2 = account2.balance()
    ending_liability2 = account2.liability()
    ending_receivable2 = account2.receivable()

    assert (ending_liability1 == starting_liability1)
    assert (ending_receivable2 == starting_receivable2)
    assert (starting_balance1 - value == ending_balance1)
    assert (starting_balance2 + value == ending_balance2)
    assert (starting_liability2 == ending_liability2)
    assert (starting_receivable1 == ending_receivable1)
예제 #14
0
def test_transactions(random_transaction, bucket):
    (transaction, account1, account2) = random_transaction

    starting_balance1 = account1.balance()
    starting_liability1 = account1.liability()
    starting_receivable1 = account1.receivable()

    starting_balance2 = account2.balance()
    starting_liability2 = account2.liability()
    starting_receivable2 = account2.receivable()

    record = Ledger.perform(transaction,
                            account1,
                            account2,
                            Authorisation(),
                            is_provisional=False,
                            bucket=bucket)

    ending_balance1 = account1.balance()
    ending_liability1 = account1.liability()
    ending_receivable1 = account1.receivable()

    ending_balance2 = account2.balance()
    ending_liability2 = account2.liability()
    ending_receivable2 = account2.receivable()

    assert (ending_balance1 == starting_balance1 - transaction.value())
    assert (ending_balance2 == starting_balance2 + transaction.value())

    assert (ending_liability1 == starting_liability1)
    assert (starting_liability2 == ending_liability2)
    assert (starting_receivable1 == ending_receivable1)
    assert (starting_receivable2 == ending_receivable2)

    assert (record.debit_account_uid() == account1.uid())
    assert (record.credit_account_uid() == account2.uid())

    debit_note = record.debit_note()
    credit_note = record.credit_note()

    assert (debit_note.account_uid() == account1.uid())
    assert (credit_note.account_uid() == account2.uid())

    assert (not debit_note.is_provisional())
    assert (not credit_note.is_provisional())

    assert (debit_note.value() == transaction.value())
    assert (credit_note.value() == transaction.value())

    now = datetime.datetime.now()

    assert (debit_note.timestamp() < now.timestamp())
    assert (credit_note.timestamp() < now.timestamp())
    assert (debit_note.timestamp() <= credit_note.timestamp())

    assert_packable(debit_note)
    assert_packable(credit_note)

    # now test refunding this transaction
    # now receipt a random amount of the transaction
    auth = Authorisation()

    refund = Refund(credit_note, auth)

    assert (not refund.is_null())
    assert (refund.authorisation() == auth)
    assert (refund.value() == transaction.value())
    assert (refund.credit_note() == credit_note)
    assert_packable(refund)

    rrecord = Ledger.refund(refund)

    assert (not rrecord.is_null())
    assert_packable(rrecord)

    assert (not rrecord.is_provisional())
    assert (rrecord.is_direct())
    assert (rrecord.get_refund_info() == refund)
    assert (rrecord.is_refund())
    assert (rrecord.original_transaction() == transaction)

    # the original transaction record has now been updated to
    # say that it has been receipted...
    assert (record.is_direct())
    record.reload()
    assert (record.is_refunded())

    assert (rrecord.original_transaction_record() == record)

    ending_balance1 = account1.balance()
    ending_liability1 = account1.liability()
    ending_receivable1 = account1.receivable()

    ending_balance2 = account2.balance()
    ending_liability2 = account2.liability()
    ending_receivable2 = account2.receivable()

    assert (ending_liability1 == starting_liability1)
    assert (ending_receivable2 == starting_receivable2)
    assert (starting_balance1 == ending_balance1)
    assert (starting_balance2 == ending_balance2)
    assert (starting_liability2 == ending_liability2)
    assert (starting_receivable1 == ending_receivable1)