| 1 | = Use-case 0003 Implementation - Deposit virtual funds =
|
|---|
| 2 |
|
|---|
| 3 | '''Initiating actor:''' Trader
|
|---|
| 4 |
|
|---|
| 5 | '''Other actors:''' —
|
|---|
| 6 |
|
|---|
| 7 | A logged-in Trader tops up their virtual cash balance in USD. This is a
|
|---|
| 8 | simulation-only operation: no real money changes hands, the amount is simply added to
|
|---|
| 9 | the Trader's `available_balance`. Every deposit is also recorded in the ledger
|
|---|
| 10 | (`transactions`) so that it appears in the transaction history
|
|---|
| 11 | ([wiki:UseCase0006Implementation UseCase0006]). The operation writes to two tables —
|
|---|
| 12 | the user row and the ledger — inside a single database transaction, so either both
|
|---|
| 13 | changes are stored or neither is. Non-numeric, zero or negative amounts are rejected
|
|---|
| 14 | before the database is touched.
|
|---|
| 15 |
|
|---|
| 16 | Original use-case description (P3): [wiki:UseCase0003].
|
|---|
| 17 | Implementation: `server/account.go`, function `Deposit`; the verification uses
|
|---|
| 18 | `ShowBalance` from the same file (the code is shown at the end of this page).
|
|---|
| 19 |
|
|---|
| 20 | Precondition: the Trader is logged in ([wiki:UseCase0002Implementation UseCase0002]);
|
|---|
| 21 | in the run below as `alice`, who starts from the seed state (available 8250.00 USD,
|
|---|
| 22 | invested 1750.00 USD).
|
|---|
| 23 |
|
|---|
| 24 | == Scenario ==
|
|---|
| 25 |
|
|---|
| 26 | 1. '''Trader''' chooses `[2] Deposit virtual funds` in the authenticated menu
|
|---|
| 27 | (types `2`).
|
|---|
| 28 | 2. '''System''' prints `-- Deposit virtual funds --` and asks `Amount (USD):`.
|
|---|
| 29 |
|
|---|
| 30 | The screenshot shows steps 1–2: the login as alice, the authenticated menu, the
|
|---|
| 31 | choice `2` and the amount prompt waiting for input.
|
|---|
| 32 |
|
|---|
| 33 | [[Image(uc0003_1_2_deposit.png)]]
|
|---|
| 34 |
|
|---|
| 35 | 3. '''Trader''' enters an amount: `500`.
|
|---|
| 36 | 4. '''System''' validates the input in Go, without accessing the database: the text
|
|---|
| 37 | must parse as a number (`strconv.ParseFloat`) and be greater than 0 (see
|
|---|
| 38 | alternate flow 4a).
|
|---|
| 39 | 5. '''System''' opens one database transaction (`db.DB.Begin()`), increments the
|
|---|
| 40 | balance, writes the ledger row and commits. Both statements run in this single
|
|---|
| 41 | transaction; if either fails, the deferred `tx.Rollback()` undoes everything.
|
|---|
| 42 | `BEGIN` and `COMMIT` are issued by Go's `Begin()` / `Commit()`; the two
|
|---|
| 43 | statements are sent exactly as in the code:
|
|---|
| 44 |
|
|---|
| 45 | {{{
|
|---|
| 46 | BEGIN;
|
|---|
| 47 |
|
|---|
| 48 | UPDATE users
|
|---|
| 49 | SET available_balance = available_balance + $1,
|
|---|
| 50 | updated_at = now()
|
|---|
| 51 | WHERE id = $2;
|
|---|
| 52 |
|
|---|
| 53 | INSERT INTO transactions (user_id, type, amount, currency, description)
|
|---|
| 54 | VALUES ($1, 'deposit', $2, 'USD', 'Virtual deposit');
|
|---|
| 55 |
|
|---|
| 56 | COMMIT;
|
|---|
| 57 | }}}
|
|---|
| 58 |
|
|---|
| 59 | Parameters: placeholders are numbered per statement. In the `UPDATE`, `$1` is the
|
|---|
| 60 | amount (`500`) and `$2` the user id (`amt, s.UserID`); in the `INSERT` it is the
|
|---|
| 61 | other way round, `$1` is the user id and `$2` the amount (`s.UserID, amt`),
|
|---|
| 62 | matching the column order.
|
|---|
| 63 |
|
|---|
| 64 | 6. '''System''' confirms `Deposited 500.0000 USD.` and returns to the authenticated
|
|---|
| 65 | menu.
|
|---|
| 66 |
|
|---|
| 67 | The screenshot shows steps 3–6: the entered amount `500`, the confirmation and the
|
|---|
| 68 | authenticated menu again.
|
|---|
| 69 |
|
|---|
| 70 | [[Image(uc0003_3_6_deposited.png)]]
|
|---|
| 71 |
|
|---|
| 72 | The statements run on the `project` schema (the connection sets
|
|---|
| 73 | `search_path=project,public`), so `users` and `transactions` mean `project.users`
|
|---|
| 74 | and `project.transactions`.
|
|---|
| 75 |
|
|---|
| 76 | === Alternate flow 4a — invalid amount ===
|
|---|
| 77 |
|
|---|
| 78 | If the amount is not a number, or is zero or negative, the system prints
|
|---|
| 79 | `Invalid amount.`; no transaction is started and nothing is written. In the run the
|
|---|
| 80 | Trader first entered `-50`. In the prototype the system then shows the
|
|---|
| 81 | authenticated menu again and the Trader chooses `[2] Deposit virtual funds` once
|
|---|
| 82 | more, which returns the scenario to step 2.
|
|---|
| 83 |
|
|---|
| 84 | [[Image(uc0003_4a_invalid.png)]]
|
|---|
| 85 |
|
|---|
| 86 | == Verification ==
|
|---|
| 87 |
|
|---|
| 88 | Right after the deposit the Trader chooses `[1] View balance` (function
|
|---|
| 89 | `ShowBalance`), which runs (`$1` = user id):
|
|---|
| 90 |
|
|---|
| 91 | {{{
|
|---|
| 92 | SELECT available_balance, invested_balance FROM users WHERE id = $1
|
|---|
| 93 | }}}
|
|---|
| 94 |
|
|---|
| 95 | It prints `Available: 8750.0000 USD`, `Invested : 1750.0000 USD`,
|
|---|
| 96 | `Total : 10500.0000 USD`. The available balance grew from the seed value 8250.00
|
|---|
| 97 | by exactly the 500.00 deposited (the rejected `-50` changed nothing), and
|
|---|
| 98 | `invested_balance` is untouched.
|
|---|
| 99 |
|
|---|
| 100 | [[Image(uc0003_verify_balance.png)]]
|
|---|
| 101 |
|
|---|
| 102 | == How to reproduce ==
|
|---|
| 103 |
|
|---|
| 104 | {{{
|
|---|
| 105 | ./eduberza -init
|
|---|
| 106 | ./eduberza
|
|---|
| 107 | # [2] Login: alice / test123
|
|---|
| 108 | # [2] Deposit virtual funds: -50 -> Invalid amount.
|
|---|
| 109 | # [2] Deposit virtual funds: 500 -> Deposited 500.0000 USD.
|
|---|
| 110 | # [1] View balance -> Available: 8750.0000 USD
|
|---|
| 111 | }}}
|
|---|
| 112 |
|
|---|
| 113 | All four screenshots come from one real run of exactly these inputs.
|
|---|
| 114 |
|
|---|
| 115 | == Source code ==
|
|---|
| 116 |
|
|---|
| 117 | `server/account.go` — `Deposit`, and `ShowBalance` used for the verification:
|
|---|
| 118 |
|
|---|
| 119 | {{{
|
|---|
| 120 | // ShowBalance prints the logged-in user's balances.
|
|---|
| 121 | func ShowBalance(s *Session) {
|
|---|
| 122 | var avail, invested float64
|
|---|
| 123 | err := db.DB.QueryRow(
|
|---|
| 124 | `SELECT available_balance, invested_balance FROM users WHERE id = $1`,
|
|---|
| 125 | s.UserID,
|
|---|
| 126 | ).Scan(&avail, &invested)
|
|---|
| 127 | if err != nil {
|
|---|
| 128 | fmt.Println("Error:", err)
|
|---|
| 129 | return
|
|---|
| 130 | }
|
|---|
| 131 | fmt.Printf("\n Available: %.4f USD\n", avail)
|
|---|
| 132 | fmt.Printf(" Invested : %.4f USD\n", invested)
|
|---|
| 133 | fmt.Printf(" Total : %.4f USD\n", avail+invested)
|
|---|
| 134 | }
|
|---|
| 135 |
|
|---|
| 136 | // Deposit - UC0003
|
|---|
| 137 | // Transactional: updates users.available_balance and inserts a ledger row.
|
|---|
| 138 | func Deposit(s *Session) {
|
|---|
| 139 | fmt.Println("\n-- Deposit virtual funds --")
|
|---|
| 140 | amtStr := prompt("Amount (USD): ")
|
|---|
| 141 | amt, err := strconv.ParseFloat(amtStr, 64)
|
|---|
| 142 | if err != nil || amt <= 0 {
|
|---|
| 143 | fmt.Println("Invalid amount.")
|
|---|
| 144 | return
|
|---|
| 145 | }
|
|---|
| 146 |
|
|---|
| 147 | tx, err := db.DB.Begin()
|
|---|
| 148 | if err != nil {
|
|---|
| 149 | fmt.Println("Error:", err)
|
|---|
| 150 | return
|
|---|
| 151 | }
|
|---|
| 152 | defer tx.Rollback()
|
|---|
| 153 |
|
|---|
| 154 | if _, err := tx.Exec(
|
|---|
| 155 | `UPDATE users
|
|---|
| 156 | SET available_balance = available_balance + $1,
|
|---|
| 157 | updated_at = now()
|
|---|
| 158 | WHERE id = $2`,
|
|---|
| 159 | amt, s.UserID,
|
|---|
| 160 | ); err != nil {
|
|---|
| 161 | fmt.Println("Error:", err)
|
|---|
| 162 | return
|
|---|
| 163 | }
|
|---|
| 164 | if _, err := tx.Exec(
|
|---|
| 165 | `INSERT INTO transactions (user_id, type, amount, currency, description)
|
|---|
| 166 | VALUES ($1, 'deposit', $2, 'USD', 'Virtual deposit')`,
|
|---|
| 167 | s.UserID, amt,
|
|---|
| 168 | ); err != nil {
|
|---|
| 169 | fmt.Println("Error:", err)
|
|---|
| 170 | return
|
|---|
| 171 | }
|
|---|
| 172 | if err := tx.Commit(); err != nil {
|
|---|
| 173 | fmt.Println("Error:", err)
|
|---|
| 174 | return
|
|---|
| 175 | }
|
|---|
| 176 | fmt.Printf("Deposited %.4f USD.\n", amt)
|
|---|
| 177 | }
|
|---|
| 178 | }}}
|
|---|