/ collect-tax.go
collect-tax.go
1 package main 2 3 import ( 4 "encoding/json" 5 "log" 6 "strconv" 7 "strings" 8 "time" 9 10 "github.com/google/uuid" 11 "github.com/maxence-charriere/go-app/v10/pkg/app" 12 shell "github.com/stateless-minds/go-ipfs-api" 13 ) 14 15 // payment is a component that holds cyber-gubi. A component is a 16 // customizable, independent, and reusable UI element. It is created by 17 // embedding app.Compo into a struct. 18 type tax struct { 19 app.Compo 20 sh *shell.Shell 21 loggedIn bool 22 userID string 23 taxUserID string 24 taxAmount int 25 wallet Wallet 26 taxWallet Wallet 27 } 28 29 func (t *tax) OnMount(ctx app.Context) { 30 sh := shell.NewShell("localhost:5001") 31 t.sh = sh 32 33 ctx.GetState("loggedIn", &t.loggedIn) 34 if !t.loggedIn { 35 ctx.Navigate("/auth") 36 } 37 38 urlPath := app.Window().URL().Path 39 40 fragments := strings.Split(urlPath, "tax/") 41 42 if len(fragments) > 1 { 43 t.taxUserID = fragments[1] 44 } 45 46 if t.taxUserID == "" { 47 ctx.Navigate("/auth") 48 } 49 50 ctx.GetState("userID", &t.userID) 51 52 ctx.GetState("balance", &t.wallet) 53 54 taxWallet, err := t.getBalance(t.taxUserID) 55 if err != nil { 56 log.Fatal("wallet not found") 57 } 58 59 t.taxWallet = taxWallet 60 } 61 62 func (t *tax) getBalance(userID string) (balance Wallet, err error) { 63 b, err := t.sh.OrbitDocsQuery(dbWallet, "_id", userID) 64 if err != nil { 65 return Wallet{}, err 66 } 67 68 if len(b) == 0 { 69 return Wallet{}, err 70 } 71 72 wallets := []Wallet{} 73 74 err = json.Unmarshal(b, &wallets) // Unmarshal the byte slice directly 75 if err != nil { 76 return Wallet{}, err 77 } 78 79 return wallets[0], nil 80 } 81 82 func (t *tax) updateBalance(userID string, balance, income int, date string) error { 83 wallet := Wallet{ 84 ID: userID, 85 Balance: balance, 86 Income: income, 87 LastReceived: date, 88 } 89 90 walletJSON, err := json.Marshal(wallet) 91 if err != nil { 92 return err 93 } 94 95 err = t.sh.OrbitDocsPut(dbWallet, walletJSON) 96 if err != nil { 97 return err 98 } 99 100 return nil 101 } 102 103 func (t *tax) storeTransaction(transaction Transaction) error { 104 transactionJSON, err := json.Marshal(transaction) 105 if err != nil { 106 return err 107 } 108 109 err = t.sh.OrbitDocsPut(dbTransaction, transactionJSON) 110 if err != nil { 111 return err 112 } 113 114 return nil 115 } 116 117 func (t *tax) withdrawTax(ctx app.Context, e app.Event) { 118 e.PreventDefault() 119 120 valid := app.Window().GetElementByID("pay-form").Call("reportValidity").Bool() 121 if valid { 122 transaction := Transaction{} 123 transaction.ID = uuid.NewString() 124 transaction.SenderID = t.taxUserID 125 transaction.ReceiverID = t.userID 126 transaction.TotalCost = t.taxAmount * 100 127 transaction.Timestamp = time.Now() 128 transaction.Date = strconv.Itoa(time.Now().Year()) + "/" + strconv.Itoa(int(time.Now().Month())) 129 130 if t.taxWallet.Balance-transaction.TotalCost < 0 { 131 ctx.Notifications().New(app.Notification{ 132 Title: "Error", 133 Body: "Not enough funds.", 134 }) 135 return 136 } 137 // update taxable balance 138 err := t.updateBalance(t.taxUserID, t.taxWallet.Balance-transaction.TotalCost, t.taxWallet.Income, t.taxWallet.LastReceived) 139 if err != nil { 140 log.Fatal(err) 141 } 142 // get treasury balance 143 receiverBalance, err := t.getBalance(t.userID) 144 if err != nil { 145 log.Fatal(err) 146 } 147 // update receiver balance 148 err = t.updateBalance(transaction.ReceiverID, receiverBalance.Balance+transaction.TotalCost, receiverBalance.Income, receiverBalance.LastReceived) 149 if err != nil { 150 // rollback sender balance 151 err := t.updateBalance(t.taxUserID, t.taxWallet.Balance+transaction.TotalCost, t.taxWallet.Income, t.taxWallet.LastReceived) 152 if err != nil { 153 log.Fatal(err) 154 } 155 return 156 } 157 // store transaction 158 err = t.storeTransaction(transaction) 159 if err != nil { 160 // rollback sender balance 161 err = t.updateBalance(t.taxUserID, t.taxWallet.Balance+transaction.TotalCost, t.taxWallet.Income, t.taxWallet.LastReceived) 162 if err != nil { 163 log.Fatal(err) 164 } 165 // rollback receiver balance 166 err = t.updateBalance(transaction.ReceiverID, receiverBalance.Balance-transaction.TotalCost, receiverBalance.Income, receiverBalance.LastReceived) 167 if err != nil { 168 log.Fatal(err) 169 } 170 return 171 } 172 173 t.wallet.Balance = t.wallet.Balance + transaction.TotalCost 174 ctx.Update() 175 176 ctx.Notifications().New(app.Notification{ 177 Title: "Success", 178 Body: "Payment successful!", 179 }) 180 } 181 } 182 183 // The Render method is where the component appearance is defined. Here, a 184 // payment form is displayed. 185 func (t *tax) Render() app.UI { 186 return app.Div().Class("container").Body( 187 app.Div().Class("mobile").Body( 188 app.Div().Class("header").Body( 189 newNav(), 190 app.Div().Class("header-summary").Body( 191 app.Span().Class("logo").Text("cyber-gubi"), 192 app.Div().Class("summary-text").Body( 193 app.Span().Text("Treasury"), 194 ), 195 app.Div().Class("summary-balance").Body( 196 app.Span().Text(strconv.Itoa(t.wallet.Balance/100)+" GUBI"), 197 ), 198 ), 199 ), 200 app.Div().ID("content").Body( 201 app.Div().Class("card").Body( 202 app.Div().Class("upper-row").Body( 203 app.Div().Class("card-item").Body( 204 app.Span().Class("span-header").Text("Collect Tax"), 205 app.Form().ID("pay-form").Body( 206 app.Label().Class("tax-label").Text("Balance"), 207 app.Input().Type("number").Value(t.taxWallet.Balance/100).Disabled(true), 208 app.Input().Type("number").Placeholder("Amount to collect").Required(true).OnChange(t.ValueTo(&t.taxAmount)), 209 app.Div().Class("drawer drawer-pay").Body( 210 app.Div().Class("menu-btn").Body( 211 app.Button().Class("submit").Type("submit").Text("Withdraw").OnClick(t.withdrawTax), 212 ), 213 ), 214 ), 215 ), 216 ), 217 ), 218 ), 219 ), 220 ) 221 }