taler-ios

iOS apps for GNU Taler (wallet)
Log | Files | Refs | README | LICENSE

Model+Deposit.swift (12681B)


      1 /*
      2  * This file is part of GNU Taler, ©2022-26 Taler Systems S.A.
      3  * See LICENSE.md
      4  */
      5 /**
      6  * @author Marc Stibane
      7  */
      8 import Foundation
      9 import taler_swift
     10 import AnyCodable
     11 //import SymLog
     12 
     13 struct AccountChange: Codable {             // Notification
     14     enum TransitionType: String, Codable {
     15         case change = "bank-account-change"
     16     }
     17     var type: TransitionType
     18     var bankAccountId: String
     19 }
     20 
     21 // MARK: - IBAN
     22 struct ValidateIbanResult: Codable {
     23     let valid: Bool
     24 }
     25 /// A request to validate an IBAN.
     26 fileprivate struct ValidateIban: WalletBackendFormattedRequest {
     27     typealias Response = ValidateIbanResult
     28     func operation() -> String { "validateIban" }
     29     func args() -> Args { Args(iban: iban) }
     30 
     31     var iban: String
     32     struct Args: Encodable {
     33         var iban: String
     34     }
     35 }
     36 extension WalletModel {
     37     /// validate IBAN. No Networking
     38     nonisolated func validateIban(_ iban: String, viewHandles: Bool = false)
     39       async throws -> Bool {
     40         let request = ValidateIban(iban: iban)
     41         let response = try await sendRequest(request, viewHandles: viewHandles)
     42         return response.valid
     43     }
     44 }
     45 // MARK: - Deposit
     46 enum PaytoType: String, Codable {
     47     case unknown
     48     case iban       // or BBAN for HUF (and CHF when taler://dev-experiment/fake-chf-bban was called)
     49     case bitcoin
     50     case cyclos
     51     case xTalerBank = "x-taler-bank"
     52 }
     53 
     54 struct IbanAccountFieldToPaytoResponse: Codable {
     55     let ok: Bool
     56     let type: String?       // either "iban" or "bban", only if ok==true
     57     let paytoUri: String?   // only if ok==true
     58 }
     59 
     60 /// A request to convert an IBAN/BBAN to PayTo.
     61 fileprivate struct IbanAccountFieldToPayto: WalletBackendFormattedRequest {
     62     typealias Response = IbanAccountFieldToPaytoResponse
     63     func operation() -> String { "convertIbanAccountFieldToPayto" }
     64     func args() -> Args { Args(value: value, currency: currency) }
     65 
     66     var value: String
     67     var currency: String
     68     struct Args: Encodable {
     69         var value: String
     70         var currency: String
     71     }
     72 }
     73 
     74 struct IbanPaytoToAccountFieldResponse: Codable {
     75     let type: String
     76     let value: String
     77 }
     78 
     79 /// A request to convert PayTo to IBAN/BBAN.
     80 fileprivate struct IbanPaytoToAccountField: WalletBackendFormattedRequest {
     81     typealias Response = IbanPaytoToAccountFieldResponse
     82     func operation() -> String { "convertIbanPaytoToAccountField" }
     83     func args() -> Args { Args(paytoUri: paytoUri) }
     84 
     85     var paytoUri: String
     86     struct Args: Encodable {
     87         var paytoUri: String
     88     }
     89 }
     90 
     91 extension WalletModel {
     92     /// convert an IBAN/BBAN to PayTo
     93     nonisolated func convertIbanAccountFieldToPayto(_ value: String, currency: String, viewHandles: Bool = false)
     94       async throws -> IbanAccountFieldToPaytoResponse {
     95         let request = IbanAccountFieldToPayto(value: value, currency: currency)
     96         let response = try await sendRequest(request, viewHandles: viewHandles)
     97         return response
     98     }
     99     nonisolated func convertIbanPaytoToAccountField(_ paytoUri: String, viewHandles: Bool = false)
    100       async throws -> IbanPaytoToAccountFieldResponse {
    101         let request = IbanPaytoToAccountField(paytoUri: paytoUri)
    102         let response = try await sendRequest(request, viewHandles: viewHandles)
    103         return response
    104     }
    105 }
    106 // MARK: - Deposit
    107 struct WireTypeDetails: Codable {
    108     let paymentTargetType: PaytoType
    109     let preferredEntryType: String?         // iban / bban  TODO: use this
    110     let talerBankHostnames: [String]?
    111 }
    112 
    113 struct DepositWireTypesResponse: Codable {
    114     /// can be used to pre-filter payment target types to offer the user as an input option
    115 //    let wireTypes: [PaytoType]
    116     let wireTypeDetails: [WireTypeDetails]
    117 }
    118 
    119 /// A request to get wire types that can be used for a deposit operation.
    120 fileprivate struct DepositWireTypes: WalletBackendFormattedRequest {
    121     typealias Response = DepositWireTypesResponse
    122     func operation() -> String { "getDepositWireTypes" }
    123     func args() -> Args { Args(currency: currency, scopeInfo: scopeInfo) }
    124 
    125     var currency: String?
    126     var scopeInfo: ScopeInfo?
    127     struct Args: Encodable {
    128         // one of these must be set - don't set both to nil
    129         var currency: String?
    130         var scopeInfo: ScopeInfo?
    131     }
    132 }
    133 extension WalletModel {
    134     /// Get wire types that can be used for a deposit operation
    135     nonisolated func depositWireTypes(_ currency: String?, scopeInfo: ScopeInfo? = nil, viewHandles: Bool = false)
    136       async throws -> [WireTypeDetails] {
    137         let request = DepositWireTypes(currency: currency, scopeInfo: scopeInfo)
    138         let response = try await sendRequest(request, viewHandles: viewHandles)
    139         return response.wireTypeDetails
    140     }
    141 }
    142 // MARK: - max Deposit amount
    143 /// Check if initiating a deposit is possible, check fees
    144 fileprivate struct AmountResponse: Codable {
    145     let effectiveAmount: Amount?
    146     let rawAmount: Amount
    147 }
    148 fileprivate struct GetMaxDepositAmount: WalletBackendFormattedRequest {
    149     typealias Response = AmountResponse
    150     func operation() -> String { "getMaxDepositAmount" }
    151     func args() -> Args { Args(currency: scope.currency, restrictScope: scope) }
    152 
    153     var scope: ScopeInfo
    154     struct Args: Encodable {
    155         var currency: String
    156         var restrictScope: ScopeInfo
    157 //        var depositPaytoUri: String
    158     }
    159 }
    160 extension WalletModel {
    161     nonisolated func getMaxDepositAmount(_ scope: ScopeInfo, viewHandles: Bool = false)
    162       async throws -> Amount {
    163         let request = GetMaxDepositAmount(scope: scope)
    164         let response = try await sendRequest(request, viewHandles: viewHandles)
    165         return response.rawAmount
    166     }
    167 } // getMaxDepositAmount
    168 // MARK: - Deposit
    169 struct DepositFees: Codable {
    170     let coin: Amount
    171     let wire: Amount
    172     let refresh: Amount
    173 }
    174 struct CheckDepositResponse: Codable {
    175     let totalDepositCost: Amount
    176     let effectiveDepositAmount: Amount
    177     let fees: DepositFees
    178     let kycSoftLimit: Amount?
    179     let kycHardLimit: Amount?
    180     let kycExchanges: [String]?                 // Base URL of exchanges that would likely require soft KYC
    181 }
    182 /// A request to get an exchange's deposit contract terms.
    183 fileprivate struct CheckDeposit: WalletBackendFormattedRequest {
    184     typealias Response = CheckDepositResponse
    185     func operation() -> String { "checkDeposit" }
    186     func args() -> Args { Args(depositPaytoUri: depositPaytoUri,
    187                                         amount: amount,
    188                           clientCancellationId: "cancel") }
    189     var depositPaytoUri: String
    190     var amount: Amount
    191     struct Args: Encodable {
    192         var depositPaytoUri: String
    193         var amount: Amount
    194         var clientCancellationId: String?
    195     }
    196 }
    197 extension WalletModel {
    198     /// check fees for deposit. No Networking
    199     nonisolated func checkDeposit4711(_ depositPaytoUri: String, amount: Amount, viewHandles: Bool = false)
    200       async throws -> CheckDepositResponse {
    201         let request = CheckDeposit(depositPaytoUri: depositPaytoUri, amount: amount)
    202         let response = try await sendRequest(request, viewHandles: viewHandles)
    203         return response
    204     }
    205 }
    206 // MARK: -
    207 struct DepositGroupResult: Decodable {
    208     var transactionId: String
    209     var txState: TransactionState?
    210 }
    211 /// A request to deposit some coins.
    212 fileprivate struct CreateDepositGroup: WalletBackendFormattedRequest {
    213     typealias Response = DepositGroupResult
    214     func operation() -> String { "createDepositGroup" }
    215     func args() -> Args { Args(depositPaytoUri: depositPaytoUri,
    216                                  restrictScope: scope,
    217                                         amount: amount) }
    218     var depositPaytoUri: String
    219     var scope: ScopeInfo
    220     var amount: Amount
    221     struct Args: Encodable {
    222         var depositPaytoUri: String
    223         var restrictScope: ScopeInfo
    224         var amount: Amount
    225     }
    226 }
    227 extension WalletModel {
    228     /// deposit coins. Networking involved
    229     nonisolated func createDepositGroup(_ depositPaytoUri: String, scope: ScopeInfo, amount: Amount, viewHandles: Bool = false)
    230       async throws -> DepositGroupResult {
    231         let request = CreateDepositGroup(depositPaytoUri: depositPaytoUri, scope: scope, amount: amount)
    232         let response = try await sendRequest(request, viewHandles: viewHandles)
    233         return response
    234     }
    235 }
    236 // MARK: -
    237 struct BankAccountsInfo: Decodable, Hashable {
    238     var bankAccountId: String
    239     var paytoUri: String
    240     var kycCompleted: Bool
    241     var currencies: [String]?       // Currencies supported by the bank, if known
    242     var label: String?
    243     var payToWorkAround: String {
    244         let payto = PayTo(paytoUri)
    245         
    246         if let cyclos = payto.cyclos {
    247             if cyclos.count > 1 {
    248                 let receiver = payto.receiver?.replacingOccurrences(of: SPACE, with: "+") ?? DEMO
    249                 return String("payto://cyclos/\(cyclos)?receiver-name=\(receiver)")
    250             }
    251         }
    252         return paytoUri
    253     }
    254 }
    255 struct BankAccounts: Decodable, Hashable {
    256     var accounts: [BankAccountsInfo]
    257 }
    258 /// A request to list known bank accounts.
    259 fileprivate struct ListBankAccounts: WalletBackendFormattedRequest {
    260     typealias Response = BankAccounts
    261     func operation() -> String { "listBankAccounts" }
    262     func args() -> Args { Args(currency: currency) }
    263     var currency: String?
    264     struct Args: Encodable {
    265         var currency: String?
    266     }
    267 }
    268 extension WalletModel {
    269     /// ask for known accounts. No networking
    270     nonisolated func listBankAccounts(_ currency: String? = nil, viewHandles: Bool = false)
    271       async throws -> [BankAccountsInfo] {
    272         let request = ListBankAccounts(currency: currency)
    273         let response = try await sendRequest(request, viewHandles: viewHandles)
    274         return response.accounts
    275     }
    276 }
    277 
    278 /// A request to get one bank account.
    279 fileprivate struct GetBankAccountById: WalletBackendFormattedRequest {
    280     typealias Response = BankAccountsInfo
    281     func operation() -> String { "getBankAccountById" }
    282     func args() -> Args { Args(bankAccountId: bankAccountId) }
    283     var bankAccountId: String
    284     struct Args: Encodable {
    285         var bankAccountId: String
    286     }
    287 }
    288 extension WalletModel {
    289     /// ask for a specific account. No networking
    290     nonisolated func getBankAccountById(_ bankAccountId: String, viewHandles: Bool = false)
    291       async throws -> BankAccountsInfo {
    292         let request = GetBankAccountById(bankAccountId: bankAccountId)
    293         let response = try await sendRequest(request, viewHandles: viewHandles)
    294         return response
    295     }
    296 }
    297 
    298 struct AddBankAccountResponse: Decodable, Hashable {
    299     var bankAccountId: String                       // Identifier of the added bank account
    300 }
    301 /// A request to add a known bank account.
    302 fileprivate struct AddBankAccount: WalletBackendFormattedRequest {
    303     typealias Response = AddBankAccountResponse
    304     func operation() -> String { "addBankAccount" }
    305     func args() -> Args { Args(paytoUri: uri,
    306                                   label: label,
    307                    replaceBankAccountId: replace) }
    308     var uri: String
    309     var label: String
    310     var replace: String?
    311     struct Args: Encodable {
    312         var paytoUri: String                        // bank account that should be added
    313         var label: String                           // Human-readable label
    314         var replaceBankAccountId: String?                // account that this new account should replace
    315     }
    316 }
    317 extension WalletModel {
    318     /// add (or update) a known account. No networking
    319     nonisolated func addBankAccount(_ uri: String,
    320                                     label: String,
    321                                   replace: String? = nil,
    322                               viewHandles: Bool = false)
    323       async throws -> String {
    324         let request = AddBankAccount(uri: uri, label: label, replace: replace)
    325         let response = try await sendRequest(request, viewHandles: viewHandles)
    326         return response.bankAccountId
    327     }
    328 }
    329 
    330 /// A request to forget a known bank account.
    331 fileprivate struct ForgetBankAccount: WalletBackendFormattedRequest {
    332     struct Response: Decodable {}   // no result - getting no error back means success
    333     func operation() -> String { "forgetBankAccount" }
    334     func args() -> Args { Args(bankAccountId: accountId) }
    335     var accountId: String
    336     struct Args: Encodable {
    337         var bankAccountId: String                        // bank account that should be forgotten
    338     }
    339 }
    340 extension WalletModel {
    341     /// add a known account. No networking
    342     nonisolated func forgetBankAccount(_ accountId: String, viewHandles: Bool = false)
    343       async throws {
    344         let request = ForgetBankAccount(accountId: accountId)
    345         _ = try await sendRequest(request, viewHandles: viewHandles)
    346     }
    347 }