sim-wallet.go (6603B)
1 package main 2 3 import ( 4 "crypto/rand" 5 "errors" 6 "fmt" 7 "strconv" 8 "strings" 9 "time" 10 ) 11 12 var C2EC_BANK_BASE_URL string 13 14 func Wallet(in chan *SimulatedPhysicalInteraction, out chan *SimulatedPhysicalInteraction, kill chan error) { 15 16 C2EC_BANK_BASE_URL = CONFIG.C2ecBaseUrl + "/taler-integration" 17 var C2EC_BANK_WITHDRAWAL_STATUS_URL = C2EC_BANK_BASE_URL + "/withdrawal-operation/:wopid" 18 var C2EC_BANK_WITHDRAWAL_REGISTRATION_URL = C2EC_BANK_BASE_URL + "/withdrawal-operation/:wopid" 19 var SIM_WALLET_LONG_POLL_MS_STR = CONFIG.WalletLongPollMs 20 21 fmt.Println("WALLET : Wallet started. Signaling terminal readiness (this is simulation specific)") 22 out <- &SimulatedPhysicalInteraction{Msg: "wallet ready"} 23 24 uriFromQrCode := <-in 25 if !CONFIG.DisableDelays { 26 time.Sleep(time.Duration(CONFIG.WalletScanQrDelay) * time.Millisecond) 27 } 28 fmt.Println("WALLET : simulated QR code scanning... scanned", uriFromQrCode) 29 wopid, err := parseTalerWithdrawUri(uriFromQrCode.Msg) 30 if err != nil { 31 fmt.Println("WALLET : failed parsing taler withdraw uri. error:", err.Error()) 32 } 33 fmt.Println("WALLET : Wallet parsed wopid:", wopid) 34 35 // Register Withdrawal 36 registrationUrl := FormatUrl( 37 C2EC_BANK_WITHDRAWAL_REGISTRATION_URL, 38 map[string]string{"wopid": wopid}, 39 map[string]string{}, 40 ) 41 42 reg := BankWithdrawalOperationPostRequest{ 43 ReservePubKey: EddsaPublicKey(simulateReservePublicKey()), 44 Amount: nil, 45 SelectedExchange: C2EC_BANK_BASE_URL, 46 } 47 48 fmt.Println("WALLET : wallet sends withdrawal registration request with freshly generated public key.") 49 fmt.Printf("HTTP : requesting POST %s\n", registrationUrl) 50 _, status, err := HttpPost[BankWithdrawalOperationPostRequest, any]( 51 registrationUrl, 52 map[string]string{ 53 "Authorization": "application/json", 54 }, 55 ®, 56 NewJsonCodec[BankWithdrawalOperationPostRequest](), 57 nil, 58 ) 59 60 if err != nil { 61 fmt.Println("WALLET : error on POST request:", err.Error()) 62 kill <- err 63 } 64 65 if status != 200 { 66 fmt.Println("WALLET : response status from registration:", status) 67 kill <- errors.New("failed registering the withdrawal parameters") 68 } 69 70 // Start long poll for confirmed or abort 71 awaitConfirmationOrAbortion := make(chan *C2ECWithdrawalStatus) 72 longPollFailed := make(chan error) 73 74 // long poll for confirmation or abortion by c2ec (whihc proves that it worked) 75 go func() { 76 url := FormatUrl( 77 C2EC_BANK_WITHDRAWAL_STATUS_URL, 78 map[string]string{"wopid": wopid}, 79 map[string]string{ 80 "long_poll_ms": SIM_WALLET_LONG_POLL_MS_STR, 81 "old_state": string(SELECTED), 82 }, 83 ) 84 println("WALLET : asking for confirmation or abortion of the withdrawal.") 85 response, status, err := HttpGet( 86 url, 87 map[string]string{"Authorization": TerminalAuth()}, 88 NewJsonCodec[C2ECWithdrawalStatus](), 89 ) 90 if err != nil { 91 kill <- err 92 return 93 } 94 if status != 200 { 95 longPollFailed <- errors.New("status of withdrawal status response was " + strconv.Itoa(status)) 96 return 97 } 98 if response.CardFees != EXCHANGE_FEES { 99 fmt.Printf("WALLET : ATTENTION -> fees do not match expected=%s, got=%s (can be ok but can also indicate a problem, especially when the fees are lower than the expected, it indicates problems.)\n", EXCHANGE_FEES, response.CardFees) 100 } else { 101 fmt.Printf("WALLET : Fees expected=%s, got=%s\n", EXCHANGE_FEES, response.CardFees) 102 } 103 awaitConfirmationOrAbortion <- response 104 }() 105 106 for { 107 select { 108 case w := <-awaitConfirmationOrAbortion: 109 fmt.Println("WALLET : payment processed:", w.Status) 110 if w.Status == CONFIRMED { 111 fmt.Println("WALLET : the exchange would now create the reserve and the wallet can withdraw the reserve") 112 } 113 if w.Status == ABORTED { 114 fmt.Println("WALLET : the withdrawal was aborted. c2ec cleans up withdrawal") 115 } 116 case f := <-longPollFailed: 117 fmt.Println("WALLET : long-polling for selection failed... error:", f.Error()) 118 kill <- f 119 } 120 } 121 } 122 123 // returns wopid. 124 func parseTalerWithdrawUri(s string) (string, error) { 125 126 wopid, found := strings.CutPrefix(s, CONFIG.TerminalQrCodeBase) 127 if !found { 128 return "", errors.New("invalid uri " + s) 129 } 130 return wopid, nil 131 } 132 133 // creates format compliant reserve public key 134 func simulateReservePublicKey() string { 135 136 mockedPubKey := make([]byte, 32) 137 _, err := rand.Read(mockedPubKey) 138 if err != nil { 139 return "" 140 } 141 return talerBinaryEncode(mockedPubKey) 142 } 143 144 type CurrencySpecification struct { 145 Name string `json:"name"` 146 Currency string `json:"currency"` 147 NumFractionalInputDigits int `json:"num_fractional_input_digits"` 148 NumFractionalNormalDigits int `json:"num_fractional_normal_digits"` 149 NumFractionalTrailingZeroDigits int `json:"num_fractional_trailing_zero_digits"` 150 AltUnitNames string `json:"alt_unit_names"` 151 } 152 153 // https://docs.taler.net/core/api-bank-integration.html#tsref-type-BankIntegrationConfig 154 type BankIntegrationConfig struct { 155 Name string `json:"name"` 156 Version string `json:"version"` 157 Implementation string `json:"implementation"` 158 Currency string `json:"currency"` 159 CurrencySpecification CurrencySpecification `json:"currency_specification"` 160 // TODO: maybe add exchanges payto uri for transfers etc.? 161 } 162 163 type BankWithdrawalOperationPostRequest struct { 164 ReservePubKey EddsaPublicKey `json:"reserve_pub"` 165 SelectedExchange string `json:"selected_exchange"` 166 Amount *Amount `json:"amount"` 167 } 168 169 type BankWithdrawalOperationPostResponse struct { 170 Status WithdrawalOperationStatus `json:"status"` 171 ConfirmTransferUrl string `json:"confirm_transfer_url"` 172 TransferDone bool `json:"transfer_done"` 173 } 174 175 type BankWithdrawalOperationStatus struct { 176 Status WithdrawalOperationStatus `json:"status"` 177 Amount string `json:"amount"` 178 CardFees string `json:"card_fees"` 179 SenderWire string `json:"sender_wire"` 180 WireTypes []string `json:"wire_types"` 181 ReservePubKey EddsaPublicKey `json:"selected_reserve_pub"` 182 SuggestedExchange string `json:"suggested_exchange"` 183 RequiredExchange string `json:"required_exchange"` 184 Aborted bool `json:"aborted"` 185 SelectionDone bool `json:"selection_done"` 186 TransferDone bool `json:"transfer_done"` 187 }