• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

hyperwallet / hyperwallet-ios-ui-sdk / 5360366649

pending completion
5360366649

push

github

web-flow
Rename polaris.yaml to polaris.yml (#345)

2252 of 2341 relevant lines covered (96.2%)

20.52 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

97.46
/Transfer/Sources/CreateTransferPresenter.swift
1
//
2
// Copyright 2018 - Present Hyperwallet
3
//
4
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software
5
// and associated documentation files (the "Software"), to deal in the Software without restriction,
6
// including without limitation the rights to use, copy, modify, merge, publish, distribute,
7
// sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
8
// furnished to do so, subject to the following conditions:
9
//
10
// The above copyright notice and this permission notice shall be included in all copies or
11
// substantial portions of the Software.
12
//
13
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING
14
// BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
15
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
16
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
17
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
18

19
#if !COCOAPODS
20
import BalanceRepository
21
import Common
22
import TransferMethodRepository
23
import TransferRepository
24
import UserRepository
25
#endif
26
import HyperwalletSDK
27

28
protocol CreateTransferView: AnyObject {
29
    func hideLoading()
30
    func notifyTransferCreated(_ transfer: HyperwalletTransfer)
31
    func reloadData()
32
    func showAlert(message: String?)
33
    func showError(_ error: HyperwalletErrorType,
34
                   pageName: String,
35
                   pageGroup: String,
36
                   _ retry: (() -> Void)?)
37
    func showLoading()
38
    func showScheduleTransfer(_ transfer: HyperwalletTransfer)
39
    func updateTransferAmountSection()
40
    func updateFooter(for section: CreateTransferController.FooterSection)
41
    func areAllFieldsValid() -> Bool
42
}
43

44
final class CreateTransferPresenter {
45
    private weak var view: CreateTransferView?
46
    private let pageName = "transfer-funds:create-transfer"
47
    private let pageGroup = "transfer-funds"
48

49
    private lazy var userRepository: UserRepository = {
23✔
50
        UserRepositoryFactory.shared.userRepository()
23✔
51
    }()
23✔
52

53
    private lazy var transferRepository: TransferRepository = {
28✔
54
        TransferRepositoryFactory.shared.transferRepository()
28✔
55
    }()
28✔
56

57
    private lazy var transferMethodRepository: TransferMethodRepository = {
35✔
58
        TransferMethodRepositoryFactory.shared.transferMethodRepository()
35✔
59
    }()
35✔
60

61
    private lazy var prepaidCardRepository: PrepaidCardRepository = {
17✔
62
        TransferMethodRepositoryFactory.shared.prepaidCardRepository()
17✔
63
    }()
17✔
64

65
    private lazy var balanceRepository: UserBalanceRepository = {
30✔
66
        BalanceRepositoryFactory.shared.userBalanceRepository()
30✔
67
    }()
30✔
68

69
    private(set) var clientTransferId: String
70
    private(set) var sectionData = [CreateTransferSectionData]()
41✔
71
    private(set) var transferSourceCellConfigurations = [TransferSourceCellConfiguration]()
41✔
72
    private(set) var availableBalance: String?
73
    private(set) var didFxQuoteChange = false
74
    private(set) var showAllAvailableSources = false
75

76
    var selectedTransferDestination: HyperwalletTransferMethod?
77
    var amount: String = "0"
78
    var notes: String?
79
    var destinationCurrency: String? {
93✔
80
        return selectedTransferDestination?.transferMethodCurrency
93✔
81
    }
93✔
82

83
    var didTapTransferAllFunds = false {
84
        didSet {
2✔
85
            if didTapTransferAllFunds {
2✔
86
                amount = availableBalance ?? "0"
2✔
87
                view?.updateTransferAmountSection()
2✔
88
            }
2✔
89
        }
2✔
90
    }
91

92
    init(_ clientTransferId: String,
93
         _ sourceToken: String?,
94
         _ showAllAvailableSources: Bool?,
95
         view: CreateTransferView) {
41✔
96
        transferSourceCellConfigurations.removeAll()
41✔
97
        self.clientTransferId = clientTransferId
41✔
98
        if let prepaidCardToken = sourceToken, prepaidCardToken.starts(with: "trm") {
41✔
99
            createTransferSourceCellConfiguration(true, .prepaidCard, prepaidCardToken)
2✔
100
        }
41✔
101
        if let showAllAvailableSources = showAllAvailableSources {
41✔
102
            self.showAllAvailableSources = showAllAvailableSources
41✔
103
        }
41✔
104
        self.view = view
41✔
105
    }
41✔
106

107
    private func initializeSections() {
34✔
108
        sectionData.removeAll()
34✔
109

34✔
110
        let createTransferSectionAmountData = CreateTransferSectionAmountData()
34✔
111
        sectionData.append(createTransferSectionAmountData)
34✔
112

34✔
113
        let createTransferSectionTransferAllData = CreateTransferSectionTransferAllData()
34✔
114
        sectionData.append(createTransferSectionTransferAllData)
34✔
115

34✔
116
        let createTransferSectionSourceData = CreateTransferSectionSourceData()
34✔
117
        sectionData.append(createTransferSectionSourceData)
34✔
118

34✔
119
        let createTransferDestinationSection = CreateTransferSectionDestinationData()
34✔
120
        sectionData.append(createTransferDestinationSection)
34✔
121

34✔
122
        let createTransferNotesSection = CreateTransferSectionNotesData()
34✔
123
        sectionData.append(createTransferNotesSection)
34✔
124

34✔
125
        let createTransferButtonData = CreateTransferSectionButtonData()
34✔
126
        sectionData.append(createTransferButtonData)
34✔
127
    }
34✔
128

129
    func loadCreateTransfer() {
41✔
130
        if showAllAvailableSources {
41✔
131
            transferSourceCellConfigurations.removeAll()
16✔
132
            loadAllAvailableSources()
16✔
133
        } else if let token =
41✔
134
            transferSourceCellConfigurations
41✔
135
                .first(where: { $0.isSelected && $0.type == .prepaidCard })?.token {
41✔
136
                loadPrepaidCardAndCreateTransfer(token: token)
2✔
137
        } else {
41✔
138
            loadUserAndCreateTransfer()
39✔
139
        }
41✔
140
    }
41✔
141

142
    func loadCreateTransferFromSelectedTransferSource(sourceToken: String) {
23✔
143
        transferSourceCellConfigurations.forEach { $0.isSelected = false }
23✔
144
        transferSourceCellConfigurations
23✔
145
            .first(where: { $0.token == sourceToken })?.isSelected = true
23✔
146
        loadTransferMethods()
23✔
147
    }
23✔
148

149
    private func loadPrepaidCardAndCreateTransfer(token: String) {
3✔
150
        view?.showLoading()
3✔
151
        prepaidCardRepository.getPrepaidCard(token: token) { [weak self] result in
3✔
152
            guard let strongSelf = self, let view = strongSelf.view else {
3✔
153
                return
×
154
            }
3✔
155
            strongSelf.view?.hideLoading()
3✔
156
            switch result {
3✔
157
            case .failure(let error):
3✔
158
                view.showError(error, pageName: strongSelf.pageName, pageGroup: strongSelf.pageGroup) {
2✔
159
                    strongSelf.loadPrepaidCardAndCreateTransfer(token: token)
1✔
160
                }
1✔
161

3✔
162
            case .success(let prepaidCard):
3✔
163
                if let prepaidCard = prepaidCard, let token = prepaidCard.token,
1✔
164
                    strongSelf.transferSourceCellConfigurations.isNotEmpty {
1✔
165
                    strongSelf.transferSourceCellConfigurations
1✔
166
                        .first(where: { $0.isSelected })?.additionalText =
1✔
167
                        prepaidCard.formattedCardBrandCardNumber
1✔
168
                    strongSelf.transferSourceCellConfigurations
1✔
169
                        .first(where: { $0.isSelected })?.destinationCurrency =
1✔
170
                        prepaidCard.transferMethodCurrency
1✔
171
                    strongSelf.loadCreateTransferFromSelectedTransferSource(sourceToken: token)
1✔
172
                }
3✔
173
            }
3✔
174
        }
3✔
175
    }
3✔
176

177
    private func loadUserAndCreateTransfer() {
24✔
178
        view?.showLoading()
24✔
179
        transferSourceCellConfigurations.removeAll()
24✔
180
        userRepository.getUser { [weak self] result in
24✔
181
            guard let strongSelf = self, let view = strongSelf.view else {
24✔
182
                return
×
183
            }
24✔
184
            view.hideLoading()
24✔
185
            switch result {
24✔
186
            case .failure(let error):
24✔
187
                view.showError(error, pageName: strongSelf.pageName, pageGroup: strongSelf.pageGroup) {
2✔
188
                    strongSelf.loadUserAndCreateTransfer()
1✔
189
                }
1✔
190

24✔
191
            case .success(let user):
24✔
192
                if let token = user?.token {
22✔
193
                    strongSelf.createTransferSourceCellConfiguration(true, .user, token)
22✔
194
                    strongSelf.loadCreateTransferFromSelectedTransferSource(sourceToken: token)
22✔
195
                }
24✔
196
            }
24✔
197
        }
24✔
198
    }
24✔
199

200
    private func createTransferSourceCellConfiguration(_ isSelectedTransferSource: Bool,
201
                                                       _ transferSourceType: TransferSourceType,
202
                                                       _ token: String,
203
                                                       _ additionalText: String? = nil,
204
                                                       _ transferMethodCurrency: String? = nil) {
55✔
205
        let configuration = TransferSourceCellConfiguration(isSelectedTransferSource: isSelectedTransferSource,
55✔
206
                                                            type: transferSourceType,
55✔
207
                                                            token: token,
55✔
208
                                                            title: transferSourceType == .user
55✔
209
                                                                ? "mobileAvailableFunds".localized() :
55✔
210
                                                                "prepaid_card".localized(),
55✔
211
                                                            fontIcon: transferSourceType == .user
55✔
212
                                                                ? .addTransferMethod : .prepaidCard)
55✔
213
        configuration.additionalText = additionalText
55✔
214
        configuration.availableBalance = availableBalance
55✔
215
        configuration.destinationCurrency = destinationCurrency
55✔
216
        if transferSourceType == .user { addCurrencyCodesToAvailableFundsConfiguration() }
55✔
217
        if transferSourceType == .prepaidCard { configuration.destinationCurrency = transferMethodCurrency }
55✔
218
        transferSourceCellConfigurations.append(configuration)
55✔
219
    }
55✔
220

221
    /// Add currency codes to available funds configuration
222
    func addCurrencyCodesToAvailableFundsConfiguration() {
32✔
223
        balanceRepository.listUserBalances(offset: 0, limit: 0) { [weak self]  (result) in
32✔
224
            guard let strongSelf = self, let view = strongSelf.view else {
32✔
225
                return
×
226
            }
32✔
227
            switch result {
32✔
228
            case .success(let balanceList):
32✔
229
                if let balanceList = balanceList, let balances = balanceList.data {
30✔
230
                    let currencies = balances
30✔
231
                        .filter({ $0.amount?.formatAmountToDouble() ?? 0 > 0 })
300✔
232
                        .map { String($0.currency!) }
90✔
233
                        .sorted()
30✔
234
                    let configuration = strongSelf.transferSourceCellConfigurations.first(where: { $0.isSelected })
30✔
235
                    if let type = configuration?.type, type == .user {
30✔
236
                        configuration?.destinationCurrency = currencies.joined(separator: ", ")
30✔
237
                    }
30✔
238
                }
32✔
239

32✔
240
            case .failure(let error):
32✔
241
                view.showError(error, pageName: strongSelf.pageName, pageGroup: strongSelf.pageGroup) {
2✔
242
                    strongSelf.addCurrencyCodesToAvailableFundsConfiguration()
1✔
243
                }
1✔
244
            }
32✔
245
        }
32✔
246
    }
32✔
247

248
    // swiftlint:disable function_body_length
249
    private func loadAllAvailableSources() {
19✔
250
        view?.showLoading()
19✔
251
        transferSourceCellConfigurations.removeAll()
19✔
252
        Hyperwallet.shared.getConfiguration { [weak self] configuration, error in
19✔
253
            guard let strongSelf = self, let view = strongSelf.view else {
19✔
254
                return
×
255
            }
19✔
256
            if let error = error {
19✔
257
                strongSelf.view?.hideLoading()
2✔
258
                view.showError(error, pageName: strongSelf.pageName, pageGroup: strongSelf.pageGroup) {
2✔
259
                    strongSelf.loadAllAvailableSources()
1✔
260
                }
1✔
261
                return
2✔
262
            }
17✔
263

17✔
264
            if let configuration = configuration, let programModel = configuration.programModel,
17✔
265
            let programModelEnum = HyperwalletProgramModel(rawValue: programModel),
17✔
266
            !programModelEnum.isPay2CardOrCardOnlyModel() {
17✔
267
                strongSelf.createTransferSourceCellConfiguration(true, .user, configuration.userToken)
9✔
268
            }
17✔
269
            strongSelf.prepaidCardRepository
17✔
270
                .listPrepaidCards(queryParam: strongSelf.setUpPrepaidCardQueryParam()) { [weak self] (result) in
17✔
271
                guard let strongSelf = self, let view = strongSelf.view else {
17✔
272
                    return
×
273
                }
17✔
274
                strongSelf.view?.hideLoading()
17✔
275
                switch result {
17✔
276
                case .success(let pageList):
17✔
277
                    var isSelectedTransferSource = false
13✔
278
                    if strongSelf.transferSourceCellConfigurations.isEmpty { isSelectedTransferSource = true }
13✔
279
                    if let prepaidCards = pageList?.data {
13✔
280
                        prepaidCards.forEach { prepaidCard in
22✔
281
                            strongSelf
22✔
282
                                .createTransferSourceCellConfiguration(isSelectedTransferSource,
22✔
283
                                                                       .prepaidCard,
22✔
284
                                                                       prepaidCard.token ?? "",
22✔
285
                                                                       prepaidCard.formattedCardBrandCardNumber,
22✔
286
                                                                       prepaidCard.transferMethodCurrency)
22✔
287
                            isSelectedTransferSource = false
22✔
288
                        }
22✔
289
                    } else if isSelectedTransferSource {
13✔
290
                        view.showAlert(message: "noTransferFromSourceAvailable".localized())
1✔
291
                        return
1✔
292
                    }
12✔
293
                    strongSelf.loadTransferMethods()
12✔
294

17✔
295
                case .failure(let error):
17✔
296
                    view.showError(error, pageName: strongSelf.pageName, pageGroup: strongSelf.pageGroup) {
4✔
297
                        strongSelf.loadAllAvailableSources()
2✔
298
                    }
2✔
299
                    return
4✔
300
                }
17✔
301
                }
17✔
302
        }
17✔
303
    }
19✔
304
    // swiftlint:enable function_body_length
305

306
    private func setUpPrepaidCardQueryParam() -> HyperwalletPrepaidCardQueryParam {
17✔
307
        let queryParam = HyperwalletPrepaidCardQueryParam()
17✔
308
        // Only fetch active prepaid cards
17✔
309
        queryParam.status = HyperwalletPrepaidCardQueryParam.QueryStatus.activated.rawValue
17✔
310
        return queryParam
17✔
311
    }
17✔
312

313
    private func loadTransferMethods() {
38✔
314
        view?.showLoading()
38✔
315
        transferMethodRepository.refreshTransferMethods()
38✔
316
        transferMethodRepository.listTransferMethods { [weak self] result in
38✔
317
            guard let strongSelf = self, let view = strongSelf.view else {
38✔
318
                return
×
319
            }
38✔
320
            view.hideLoading()
38✔
321
            switch result {
38✔
322
            case .failure(let error):
38✔
323
                view.showError(error, pageName: strongSelf.pageName, pageGroup: strongSelf.pageGroup) {
6✔
324
                    strongSelf.loadTransferMethods()
3✔
325
                }
3✔
326

38✔
327
            case .success(let result):
38✔
328
                var transferMethods = result?.data
32✔
329
                if strongSelf.transferSourceCellConfigurations.first(where: {
32✔
330
                    $0.isSelected
32✔
331
                })?.type == .prepaidCard {
32✔
332
                    let prepaidCardType = HyperwalletTransferMethod.TransferMethodType.prepaidCard.rawValue
5✔
333
                    transferMethods?.removeAll(where: {
25✔
334
                        $0.type == prepaidCardType
25✔
335
                    })
25✔
336
                }
32✔
337
                if strongSelf.selectedTransferDestination == nil {
32✔
338
                    strongSelf.selectedTransferDestination = transferMethods?.first
32✔
339
                }
32✔
340
                strongSelf.createInitialTransfer()
32✔
341
            }
38✔
342
        }
38✔
343
    }
38✔
344

345
    private func createInitialTransfer() {
34✔
346
        availableBalance = nil
34✔
347
        guard let sourceToken =
34✔
348
            transferSourceCellConfigurations.first(where: { $0.isSelected })?.token,
34✔
349
            let destinationToken = selectedTransferDestination?.token,
34✔
350
            let destinationCurrency = destinationCurrency else {
34✔
351
                initializeSections()
4✔
352
                view?.reloadData()
4✔
353
                return
4✔
354
        }
30✔
355
        view?.showLoading()
30✔
356
        let transfer = HyperwalletTransfer.Builder(clientTransferId: clientTransferId,
30✔
357
                                                   sourceToken: sourceToken,
30✔
358
                                                   destinationToken: destinationToken)
30✔
359
            .destinationCurrency(destinationCurrency)
30✔
360
            .build()
30✔
361

30✔
362
        transferRepository.createTransfer(transfer) { [weak self] result in
30✔
363
            guard let strongSelf = self, let view = strongSelf.view else {
30✔
364
                return
×
365
            }
30✔
366
            view.hideLoading()
30✔
367
            switch result {
30✔
368
            case .failure(let error):
30✔
369
                if error.group != .business {
7✔
370
                    view.showError(error, pageName: strongSelf.pageName, pageGroup: strongSelf.pageGroup) {
4✔
371
                        strongSelf.createInitialTransfer()
2✔
372
                    }
2✔
373
                }
30✔
374

30✔
375
            case .success(let transfer):
30✔
376
                strongSelf.availableBalance = transfer?.destinationAmount
23✔
377
                if strongSelf.didTapTransferAllFunds { strongSelf.amount = strongSelf.availableBalance ?? "0" }
23✔
378
                strongSelf.transferSourceCellConfigurations.forEach {
31✔
379
                    $0.availableBalance = transfer?.destinationAmount
31✔
380
                }
31✔
381
            }
30✔
382
            strongSelf.initializeSections()
30✔
383
            view.reloadData()
30✔
384
        }
30✔
385
    }
30✔
386

387
    // MARK: - Create Transfer Button Tapped
388
    func createTransfer() {
6✔
389
        guard let view = view, view.areAllFieldsValid() else {
6✔
390
            return
×
391
        }
6✔
392

6✔
393
        if let sourceToken =
6✔
394
            transferSourceCellConfigurations.first(where: { $0.isSelected })?.token,
6✔
395
            let destinationToken = selectedTransferDestination?.token,
6✔
396
            let destinationCurrency = destinationCurrency {
6✔
397
            view.showLoading()
6✔
398
            let transfer = HyperwalletTransfer.Builder(clientTransferId: clientTransferId,
6✔
399
                                                       sourceToken: sourceToken,
6✔
400
                                                       destinationToken: destinationToken)
6✔
401
                .destinationAmount(didTapTransferAllFunds ? nil : amount)
6✔
402
                .notes(notes)
6✔
403
                .destinationCurrency(destinationCurrency)
6✔
404
                .build()
6✔
405

6✔
406
            transferRepository.createTransfer(transfer) { [weak self] result in
6✔
407
                guard let strongSelf = self, let view = strongSelf.view else {
6✔
408
                    return
×
409
                }
6✔
410
                view.hideLoading()
6✔
411
                switch result {
6✔
412
                case .failure(let error):
6✔
413
                    strongSelf.errorHandler(for: error) {
4✔
414
                        view.showError(error, pageName: strongSelf.pageName, pageGroup: strongSelf.pageGroup) {
2✔
415
                            strongSelf.createTransfer()
1✔
416
                        }
1✔
417
                    }
2✔
418

6✔
419
                case .success(let transfer):
6✔
420
                    if let transfer = transfer {
2✔
421
                        if strongSelf.didTapTransferAllFunds &&
2✔
422
                            transfer.destinationAmount != strongSelf.availableBalance {
2✔
423
                            strongSelf.didFxQuoteChange = true
×
424
                        }
2✔
425
                        view.notifyTransferCreated(transfer)
2✔
426
                        view.showScheduleTransfer(transfer)
2✔
427
                    }
6✔
428
                }
6✔
429
            }
6✔
430
        }
6✔
431
    }
6✔
432

433
    func resetErrorMessagesForAllSections() {
3✔
434
        if sectionData.contains(where: { $0.errorMessage != nil }) {
13✔
435
            sectionData.filter({ $0.errorMessage != nil }).forEach({
6✔
436
                $0.errorMessage = nil
6✔
437
                switch $0.createTransferSectionHeader {
6✔
438
                case .amount:
6✔
439
                    view?.updateFooter(for: .amount)
1✔
440

6✔
441
                case .button:
6✔
442
                    view?.updateFooter(for: .button)
1✔
443

6✔
444
                case .destination:
6✔
445
                    view?.updateFooter(for: .destination)
1✔
446

6✔
447
                case .notes:
6✔
448
                    view?.updateFooter(for: .notes)
1✔
449

6✔
450
                case .transferAll:
6✔
451
                    view?.updateFooter(for: .transferAll)
1✔
452

6✔
453
                case .source:
6✔
454
                    view?.updateFooter(for: .source)
1✔
455
                }
6✔
456
            })
6✔
457
        }
3✔
458
    }
3✔
459

460
    private func errorHandler(for error: HyperwalletErrorType, _ nonBusinessErrorHandler: @escaping () -> Void) {
4✔
461
        switch error.group {
4✔
462
        case .business:
4✔
463
            resetErrorMessagesForAllSections()
2✔
464
            if let errors = error.getHyperwalletErrors()?.errorList, errors.isNotEmpty {
2✔
465
                updateFooterContent(errors)
2✔
466
                if errors.contains(where: { $0.fieldName == nil }) {
2✔
467
                    view?.showError(error, pageName: pageName, pageGroup: pageGroup, nil)
1✔
468
                }
2✔
469
            }
4✔
470

4✔
471
        default:
4✔
472
            nonBusinessErrorHandler()
2✔
473
        }
4✔
474
    }
4✔
475

476
    private func updateFooterContent(_ errors: [HyperwalletError]) {
2✔
477
        for error in errors {
2✔
478
            if let sectionData = sectionData.first(where: { $0.createTransferSectionHeader == .transferAll }),
4✔
479
               error.fieldName != nil {
2✔
480
                sectionData.errorMessage = error.message
1✔
481
                view?.updateFooter(for: .transferAll)
1✔
482
            }
2✔
483
        }
2✔
484
    }
2✔
485
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc