taler-ios

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

Model+Deposit.swift (12523B)


      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 talerBankHostnames: [String]
    110 }
    111 
    112 struct DepositWireTypesResponse: Codable {
    113     /// can be used to pre-filter payment target types to offer the user as an input option
    114 //    let wireTypes: [PaytoType]
    115     let wireTypeDetails: [WireTypeDetails]
    116 }
    117 
    118 /// A request to get wire types that can be used for a deposit operation.
    119 fileprivate struct DepositWireTypes: WalletBackendFormattedRequest {
    120     typealias Response = DepositWireTypesResponse
    121     func operation() -> String { "getDepositWireTypes" }
    122     func args() -> Args { Args(currency: currency, scopeInfo: scopeInfo) }
    123 
    124     var currency: String?
    125     var scopeInfo: ScopeInfo?
    126     struct Args: Encodable {
    127         // one of these must be set - don't set both to nil
    128         var currency: String?
    129         var scopeInfo: ScopeInfo?
    130     }
    131 }
    132 extension WalletModel {
    133     /// Get wire types that can be used for a deposit operation
    134     nonisolated func depositWireTypes(_ currency: String?, scopeInfo: ScopeInfo? = nil, viewHandles: Bool = false)
    135       async throws -> [WireTypeDetails] {
    136         let request = DepositWireTypes(currency: currency, scopeInfo: scopeInfo)
    137         let response = try await sendRequest(request, viewHandles: viewHandles)
    138         return response.wireTypeDetails
    139     }
    140 }
    141 // MARK: - max Deposit amount
    142 /// Check if initiating a deposit is possible, check fees
    143 fileprivate struct AmountResponse: Codable {
    144     let effectiveAmount: Amount?
    145     let rawAmount: Amount
    146 }
    147 fileprivate struct GetMaxDepositAmount: WalletBackendFormattedRequest {
    148     typealias Response = AmountResponse
    149     func operation() -> String { "getMaxDepositAmount" }
    150     func args() -> Args { Args(currency: scope.currency, restrictScope: scope) }
    151 
    152     var scope: ScopeInfo
    153     struct Args: Encodable {
    154         var currency: String
    155         var restrictScope: ScopeInfo
    156 //        var depositPaytoUri: String
    157     }
    158 }
    159 extension WalletModel {
    160     nonisolated func getMaxDepositAmount(_ scope: ScopeInfo, viewHandles: Bool = false)
    161       async throws -> Amount {
    162         let request = GetMaxDepositAmount(scope: scope)
    163         let response = try await sendRequest(request, viewHandles: viewHandles)
    164         return response.rawAmount
    165     }
    166 } // getMaxDepositAmount
    167 // MARK: - Deposit
    168 struct DepositFees: Codable {
    169     let coin: Amount
    170     let wire: Amount
    171     let refresh: Amount
    172 }
    173 struct CheckDepositResponse: Codable {
    174     let totalDepositCost: Amount
    175     let effectiveDepositAmount: Amount
    176     let fees: DepositFees
    177     let kycSoftLimit: Amount?
    178     let kycHardLimit: Amount?
    179     let kycExchanges: [String]?                 // Base URL of exchanges that would likely require soft KYC
    180 }
    181 /// A request to get an exchange's deposit contract terms.
    182 fileprivate struct CheckDeposit: WalletBackendFormattedRequest {
    183     typealias Response = CheckDepositResponse
    184     func operation() -> String { "checkDeposit" }
    185     func args() -> Args { Args(depositPaytoUri: depositPaytoUri,
    186                                         amount: amount,
    187                           clientCancellationId: "cancel") }
    188     var depositPaytoUri: String
    189     var amount: Amount
    190     struct Args: Encodable {
    191         var depositPaytoUri: String
    192         var amount: Amount
    193         var clientCancellationId: String?
    194     }
    195 }
    196 extension WalletModel {
    197     /// check fees for deposit. No Networking
    198     nonisolated func checkDeposit4711(_ depositPaytoUri: String, amount: Amount, viewHandles: Bool = false)
    199       async throws -> CheckDepositResponse {
    200         let request = CheckDeposit(depositPaytoUri: depositPaytoUri, amount: amount)
    201         let response = try await sendRequest(request, viewHandles: viewHandles)
    202         return response
    203     }
    204 }
    205 // MARK: -
    206 struct DepositGroupResult: Decodable {
    207     var transactionId: String
    208     var txState: TransactionState?
    209 }
    210 /// A request to deposit some coins.
    211 fileprivate struct CreateDepositGroup: WalletBackendFormattedRequest {
    212     typealias Response = DepositGroupResult
    213     func operation() -> String { "createDepositGroup" }
    214     func args() -> Args { Args(depositPaytoUri: depositPaytoUri,
    215                                  restrictScope: scope,
    216                                         amount: amount) }
    217     var depositPaytoUri: String
    218     var scope: ScopeInfo
    219     var amount: Amount
    220     struct Args: Encodable {
    221         var depositPaytoUri: String
    222         var restrictScope: ScopeInfo
    223         var amount: Amount
    224     }
    225 }
    226 extension WalletModel {
    227     /// deposit coins. Networking involved
    228     nonisolated func createDepositGroup(_ depositPaytoUri: String, scope: ScopeInfo, amount: Amount, viewHandles: Bool = false)
    229       async throws -> DepositGroupResult {
    230         let request = CreateDepositGroup(depositPaytoUri: depositPaytoUri, scope: scope, amount: amount)
    231         let response = try await sendRequest(request, viewHandles: viewHandles)
    232         return response
    233     }
    234 }
    235 // MARK: -
    236 struct BankAccountsInfo: Decodable, Hashable {
    237     var bankAccountId: String
    238     var paytoUri: String
    239     var kycCompleted: Bool
    240     var label: String?
    241     var payToWorkAround: String {
    242         let payto = PayTo(paytoUri)
    243         
    244         if let cyclos = payto.cyclos {
    245             if cyclos.count > 1 {
    246                 let receiver = payto.receiver?.replacingOccurrences(of: SPACE, with: "+") ?? DEMO
    247                 return String("payto://cyclos/\(cyclos)?receiver-name=\(receiver)")
    248             }
    249         }
    250         return paytoUri
    251     }
    252 }
    253 struct BankAccounts: Decodable, Hashable {
    254     var accounts: [BankAccountsInfo]
    255 }
    256 /// A request to list known bank accounts.
    257 fileprivate struct ListBankAccounts: WalletBackendFormattedRequest {
    258     typealias Response = BankAccounts
    259     func operation() -> String { "listBankAccounts" }
    260     func args() -> Args { Args(currency: currency) }
    261     var currency: String?
    262     struct Args: Encodable {
    263         var currency: String?
    264     }
    265 }
    266 extension WalletModel {
    267     /// ask for known accounts. No networking
    268     nonisolated func listBankAccounts(_ currency: String? = nil, viewHandles: Bool = false)
    269       async throws -> [BankAccountsInfo] {
    270         let request = ListBankAccounts(currency: currency)
    271         let response = try await sendRequest(request, viewHandles: viewHandles)
    272         return response.accounts
    273     }
    274 }
    275 
    276 /// A request to get one bank account.
    277 fileprivate struct GetBankAccountById: WalletBackendFormattedRequest {
    278     typealias Response = BankAccountsInfo
    279     func operation() -> String { "getBankAccountById" }
    280     func args() -> Args { Args(bankAccountId: bankAccountId) }
    281     var bankAccountId: String
    282     struct Args: Encodable {
    283         var bankAccountId: String
    284     }
    285 }
    286 extension WalletModel {
    287     /// ask for a specific account. No networking
    288     nonisolated func getBankAccountById(_ bankAccountId: String, viewHandles: Bool = false)
    289       async throws -> BankAccountsInfo {
    290         let request = GetBankAccountById(bankAccountId: bankAccountId)
    291         let response = try await sendRequest(request, viewHandles: viewHandles)
    292         return response
    293     }
    294 }
    295 
    296 struct AddBankAccountResponse: Decodable, Hashable {
    297     var bankAccountId: String                       // Identifier of the added bank account
    298 }
    299 /// A request to add a known bank account.
    300 fileprivate struct AddBankAccount: WalletBackendFormattedRequest {
    301     typealias Response = AddBankAccountResponse
    302     func operation() -> String { "addBankAccount" }
    303     func args() -> Args { Args(paytoUri: uri,
    304                                   label: label,
    305                    replaceBankAccountId: replace) }
    306     var uri: String
    307     var label: String
    308     var replace: String?
    309     struct Args: Encodable {
    310         var paytoUri: String                        // bank account that should be added
    311         var label: String                           // Human-readable label
    312         var replaceBankAccountId: String?                // account that this new account should replace
    313     }
    314 }
    315 extension WalletModel {
    316     /// add (or update) a known account. No networking
    317     nonisolated func addBankAccount(_ uri: String,
    318                                     label: String,
    319                                   replace: String? = nil,
    320                               viewHandles: Bool = false)
    321       async throws -> String {
    322         let request = AddBankAccount(uri: uri, label: label, replace: replace)
    323         let response = try await sendRequest(request, viewHandles: viewHandles)
    324         return response.bankAccountId
    325     }
    326 }
    327 
    328 /// A request to forget a known bank account.
    329 fileprivate struct ForgetBankAccount: WalletBackendFormattedRequest {
    330     struct Response: Decodable {}   // no result - getting no error back means success
    331     func operation() -> String { "forgetBankAccount" }
    332     func args() -> Args { Args(bankAccountId: accountId) }
    333     var accountId: String
    334     struct Args: Encodable {
    335         var bankAccountId: String                        // bank account that should be forgotten
    336     }
    337 }
    338 extension WalletModel {
    339     /// add a known account. No networking
    340     nonisolated func forgetBankAccount(_ accountId: String, viewHandles: Bool = false)
    341       async throws {
    342         let request = ForgetBankAccount(accountId: accountId)
    343         _ = try await sendRequest(request, viewHandles: viewHandles)
    344     }
    345 }