cashless2ecash

cashless2ecash: pay with cards for digital cash (experimental)
Log | Files | Refs | README

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 }