cashless2ecash

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

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 		&reg,
     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 }