All Downloads are FREE. Search and download functionalities are using the official Maven repository.

sss.openstar.wallet.ClientWallet.scala Maven / Gradle / Ivy

package sss.openstar.wallet

import sss.db._
import sss.openstar.BusEvent
import sss.openstar.ledger.LedgerId
import sss.openstar.util.Amount
import sss.openstar.schemamigration.SqlSchemaNames.ColumnNames._
import sss.db.ops.DbOps.{DbRunOps, OptFutureTxOps}
import sss.openstar.schemamigration.SqlSchemaNames.TableNames.clientWalletTableName

import scala.util.Try

object ClientWallet {

  case class ClientBalance(ledgerId: LedgerId, walletOwner: String, client: String, balance: Amount)

  case class NewClientBalance(ledgerId: LedgerId,
                              walletOwner: String,
                              client: String,
                              balance: Amount,
                              delta: Amount
  ) extends BusEvent
}

/**
 * This class includes the persistence mechanism
 * NB- Not reentrant!! An unlocked wallet should only be used
 * by a single client
 * @param walletOwner
 * @param db
 */
class ClientWallet(walletOwner: String)(implicit db: Db) {

  private[wallet] lazy val clientAccountsTable = db.table(clientWalletTableName)

  private[wallet] def total: Long = {
    db
      .select(s"SELECT SUM($amountCol) as total FROM ${clientAccountsTable.name}")
      .find(where(ownerCol -> walletOwner))
      .map(_.flatMap(_.bigDecimalOpt("total")).fold(0L)(_.longValue()))
      .dbRunSyncGet
  }

  private[wallet] def balance(client: String): Option[Long] = balanceFTx(client).dbRunSyncGet

  private[wallet] def balanceFTx(client: String): FutureTx[Option[Long]] =
    clientAccountsTable
      .find(
        where(
          ownerCol -> walletOwner,
          clientCol -> client
        )
      )
      .map(_.map(_.long(amountCol)))

  private[wallet] def listClients(startIndex: Long, pageSize: Int): Seq[Row] = {
    val rows = clientAccountsTable
      .filter(where(ownerCol -> walletOwner).limit(startIndex, pageSize))
    rows
  }.dbRunSyncGet

  private[wallet] def transfer(amount: Long, fromClient: String, toClient: String): Try[Long] = (for {

      _ <- debit(amount, fromClient)
      bal <- credit(amount, toClient)

  } yield bal).dbRunSync

  private[wallet] def lodge(amount: Long, toClient: String): Try[Long] =  {
    credit(amount, toClient).dbRunSync
  }

  private[wallet] def withdraw(amount: Long, fromClient: String): Try[Long] = {
    debit(amount, fromClient).dbRunSync
  }

  private[wallet] def withdrawAll(fromClient: String): Try[Long] = (for {
    balOpt <- balanceFTx(fromClient)
    newBal <- balOpt.map(debit(_, fromClient)).toFutureTxOpt
  } yield newBal)
    .dbRunSync map {
    case None => throw new IllegalArgumentException(s"No such client $fromClient")
    case (Some(value)) => value
  }


  private[wallet] def credit(amount: Long, client: String): FutureTx[Long] = for {

    foundOpt <- clientAccountsTable.find(where(ownerCol -> walletOwner, clientCol -> client))
    _ = require(amount > 0, "Cannot credit 0 to account")
    resultBalance <- foundOpt match {
      case None =>
        clientAccountsTable.insert(
          Map(
            ownerCol -> walletOwner,
            amountCol -> amount,
            clientCol -> client
          )
        ).map(_ => amount)

      case Some(row) =>
        val existingBalance = row.long(amountCol)
        val newBalance = existingBalance + amount
        clientAccountsTable.update(
          Map(
            amountCol -> newBalance
          ),
          where(ownerCol -> walletOwner, clientCol -> client),
          updateVersionCol = false
        ).map(_ => newBalance)

    }
  } yield resultBalance

  private def debit(amount: Long, client: String): FutureTx[Long] = for {
    rowOpt <- clientAccountsTable.find(where(ownerCol -> walletOwner, clientCol -> client))
    _ = require(rowOpt.isDefined, s"Cannot debit from non existent client $client")
    row = rowOpt.get
    existingBalance = row.long(amountCol)
    _ = require(existingBalance >= amount, s"Existing balance ($existingBalance) must exceed amount ($amount)")
    newBalance = existingBalance - amount

    _ <- if (newBalance == 0) {
      clientAccountsTable.delete(
        where(idCol -> row.id)
      )
    } else {
      clientAccountsTable.updateRow(
        Map(
          amountCol -> newBalance,
          idCol -> row.id
        )
      )
    }

  } yield newBalance
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy