sim-terminal.go (7623B)
1 package main 2 3 import ( 4 "encoding/base64" 5 "errors" 6 "fmt" 7 "strconv" 8 "time" 9 10 "github.com/gofrs/uuid" 11 ) 12 13 // retrieved from the cli tool when added the terminal 14 var TERMINAL_USER_ID string 15 16 var EXCHANGE_FEES string 17 18 func Terminal(in chan *SimulatedPhysicalInteraction, out chan *SimulatedPhysicalInteraction, kill chan error) { 19 20 var C2EC_TERMINAL_CONFIG_API = CONFIG.C2ecBaseUrl + "/config" 21 var C2EC_TERMINAL_SETUP_WITHDRAWAL_API = CONFIG.C2ecBaseUrl + "/withdrawals" 22 var C2EC_TERMINAL_STATUS_WITHDRAWAL_API = CONFIG.C2ecBaseUrl + "/withdrawals/:wopid" 23 var C2EC_TERMINAL_CHECK_WITHDRAWAL_API = CONFIG.C2ecBaseUrl + "/withdrawals/:wopid/check" 24 TERMINAL_USER_ID = "Simulation-" + CONFIG.TerminalId 25 26 fmt.Println("TERMINAL: Terminal idle... awaiting readiness message of sim-wallet") 27 <-in 28 29 fmt.Println("TERMINAL: basic auth header:", TerminalAuth()) 30 fmt.Println("TERMINAL: loading terminal api config") 31 terminalApiCfg, status, err := HttpGet( 32 C2EC_TERMINAL_CONFIG_API, 33 map[string]string{"Authorization": TerminalAuth()}, 34 NewJsonCodec[TerminalConfig](), 35 ) 36 if err != nil { 37 kill <- err 38 return 39 } 40 if status != 200 { 41 kill <- errors.New("terminal api configuration failed with status " + strconv.Itoa(status)) 42 return 43 } 44 45 EXCHANGE_FEES = terminalApiCfg.WithdrawalFees 46 47 fmt.Println("TERMINAL: API config loaded.", terminalApiCfg.Name, terminalApiCfg.Version, terminalApiCfg.ProviderName, terminalApiCfg.WireType, EXCHANGE_FEES) 48 49 fmt.Println("TERMINAL: Sim-Wallet ready, intiating withdrawal...") 50 51 uuid, err := uuid.NewGen().NewV7() 52 if err != nil { 53 kill <- err 54 return 55 } 56 57 setupReq := &TerminalWithdrawalSetup{ 58 Amount: CONFIG.Amount, 59 SuggestedAmount: "", 60 ProviderTransactionId: "", 61 TerminalFees: EXCHANGE_FEES, 62 RequestUid: uuid.String(), 63 UserUuid: "", 64 Lock: "", 65 } 66 67 url := FormatUrl( 68 C2EC_TERMINAL_SETUP_WITHDRAWAL_API, 69 map[string]string{}, 70 map[string]string{}, 71 ) 72 fmt.Println("TERMINAL: requesting url:", url) 73 response, status, err := HttpPost( 74 url, 75 map[string]string{"Authorization": TerminalAuth()}, 76 setupReq, 77 NewJsonCodec[TerminalWithdrawalSetup](), 78 NewJsonCodec[TerminalWithdrawalSetupResponse](), 79 ) 80 if err != nil { 81 kill <- err 82 return 83 } 84 if status != 200 { 85 kill <- errors.New("status of withdrawal setup response was " + strconv.Itoa(status)) 86 return 87 } 88 89 wopidEncoded := response.Wopid 90 fmt.Println("TERMINAL: received wopid:", wopidEncoded) 91 92 // this decoding encoding cycle is useless but tests 93 // decoding and encoding of the wopid. That's why it is 94 // done here. 95 wopidDecoded, err := ParseWopid(wopidEncoded) 96 if err != nil { 97 kill <- err 98 return 99 } 100 wopidEncoded = FormatWopid(wopidDecoded) 101 102 uri := CONFIG.TerminalQrCodeBase + wopidEncoded 103 fmt.Println("TERMINAL: Taler Withdrawal URI:", uri) 104 105 // note for realworld implementation 106 // -> start long polling always before showing the QR code 107 awaitSelection := make(chan *BankWithdrawalOperationStatus) 108 longPollFailed := make(chan error) 109 110 fmt.Println("TERMINAL: now sending long poll request to c2ec from terminal and await parameter selection") 111 go func() { 112 113 url := FormatUrl( 114 C2EC_TERMINAL_STATUS_WITHDRAWAL_API, 115 map[string]string{"wopid": wopidEncoded}, 116 map[string]string{"long_poll_ms": CONFIG.TerminalLongPollMs}, 117 ) 118 fmt.Println("TERMINAL: requesting status update for withdrawal", url) 119 response, status, err := HttpGet( 120 url, 121 map[string]string{"Authorization": TerminalAuth()}, 122 NewJsonCodec[BankWithdrawalOperationStatus](), 123 ) 124 if err != nil { 125 kill <- err 126 return 127 } 128 if status != 200 { 129 longPollFailed <- errors.New("status of withdrawal status response was " + strconv.Itoa(status)) 130 return 131 } 132 133 fmt.Printf("TERMINAL: wallet should use exchange=%s\n", response.RequiredExchange) 134 awaitSelection <- response 135 }() 136 137 fmt.Println("need to sleep a bit that long polling request is guaranteed to be executed before the POST of the registration. This won't be a problem in real world appliance.") 138 time.Sleep(time.Duration(10) * time.Millisecond) 139 140 if !CONFIG.DisableDelays { 141 fmt.Println("TERMINAL: simulating QR Code scan. delay:", CONFIG.WalletScanQrDelay) 142 time.Sleep(time.Duration(CONFIG.WalletScanQrDelay) * time.Millisecond) 143 } else { 144 fmt.Println("TERMINAL: simulating QR Code scan.") 145 } 146 out <- &SimulatedPhysicalInteraction{Msg: uri} 147 for { 148 select { 149 case w := <-awaitSelection: 150 fmt.Println("TERMINAL: parameters selected:", w.ReservePubKey) 151 if !CONFIG.DisableDelays { 152 fmt.Println("TERMINAL: simulating user interaction. customer presents card. delay:", CONFIG.TerminalAcceptCardDelay) 153 time.Sleep(time.Duration(CONFIG.TerminalAcceptCardDelay) * time.Millisecond) 154 } else { 155 fmt.Println("TERMINAL: simulating user interaction. customer presents card.") 156 } 157 if !CONFIG.DisableDelays { 158 fmt.Println("TERMINAL: card accepted. terminal waits for response of provider backend. delay:", CONFIG.ProviderBackendPaymentDelay) 159 time.Sleep(time.Duration(CONFIG.ProviderBackendPaymentDelay) * time.Millisecond) 160 } else { 161 fmt.Println("TERMINAL: card accepted. terminal waits for response of provider backend.") 162 } 163 164 fmt.Println("TERMINAL: payment was processed at the provider backend. sending check notification.") 165 checkNotification := &TerminalWithdrawalConfirmationRequest{ 166 ProviderTransactionId: uuid.String(), 167 TerminalFees: EXCHANGE_FEES, 168 } 169 checkurl := FormatUrl( 170 C2EC_TERMINAL_CHECK_WITHDRAWAL_API, 171 map[string]string{"wopid": wopidEncoded}, 172 map[string]string{}, 173 ) 174 fmt.Println("TERMINAL: check url", checkurl) 175 _, status, err = HttpPost[TerminalWithdrawalConfirmationRequest, any]( 176 checkurl, 177 map[string]string{"Authorization": TerminalAuth()}, 178 checkNotification, 179 NewJsonCodec[TerminalWithdrawalConfirmationRequest](), 180 nil, 181 ) 182 if err != nil { 183 fmt.Println("TERMINAL: error on POST request:", err.Error()) 184 kill <- err 185 } 186 if status != 204 { 187 fmt.Println("TERMINAL: error while check payment POST: " + strconv.Itoa(status)) 188 kill <- errors.New("payment check request by terminal failed") 189 } 190 fmt.Println("TERMINAL: Terminal flow ended succesful") 191 return 192 case f := <-longPollFailed: 193 fmt.Println("TERMINAL: long-polling for selection failed... error:", err) 194 kill <- f 195 } 196 } 197 } 198 199 func TerminalAuth() string { 200 201 userAndPw := fmt.Sprintf("%s:%s", TERMINAL_USER_ID, CONFIG.TerminalAccessToken) 202 return "Basic " + base64.StdEncoding.EncodeToString([]byte(userAndPw)) 203 } 204 205 // Structs copied from c2ec 206 type TerminalConfig struct { 207 Name string `json:"name"` 208 Version string `json:"version"` 209 ProviderName string `json:"provider_name"` 210 Currency string `json:"currency"` 211 WithdrawalFees string `json:"withdrawal_fees"` 212 WireType string `json:"wire_type"` 213 } 214 215 type TerminalWithdrawalSetup struct { 216 Amount string `json:"amount"` 217 SuggestedAmount string `json:"suggested_amount"` 218 ProviderTransactionId string `json:"provider_transaction_id"` 219 TerminalFees string `json:"terminal_fees"` 220 RequestUid string `json:"request_uid"` 221 UserUuid string `json:"user_uuid"` 222 Lock string `json:"lock"` 223 } 224 225 type TerminalWithdrawalSetupResponse struct { 226 Wopid string `json:"withdrawal_id"` 227 } 228 229 type TerminalWithdrawalConfirmationRequest struct { 230 ProviderTransactionId string `json:"provider_transaction_id"` 231 TerminalFees string `json:"terminal_fees"` 232 UserUuid string `json:"user_uuid"` 233 Lock string `json:"lock"` 234 }