• 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

95.5
/TransferMethod/Sources/UpdateTransferMethodPresenter.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
import HyperwalletSDK
20
#if !COCOAPODS
21
import Common
22
import TransferMethodRepository
23
#endif
24

25
protocol UpdateTransferMethodView: AnyObject {
26
    func fieldValues() -> [(name: String, value: String)]
27
    func dismissProcessing(handler: @escaping () -> Void)
28
    func hideLoading()
29
    func areAllUpdatedFieldsValid() -> Bool
30
    func notifyTransferMethodUpdated(_ transferMethod: HyperwalletTransferMethod)
31
    func showConfirmation(handler: @escaping () -> Void)
32
    func showError( title: String, message: String)
33
    func showError(_ error: HyperwalletErrorType,
34
                   pageName: String,
35
                   pageGroup: String,
36
                   _ retry: (() -> Void)?)
37
    func showLoading()
38
    func showProcessing()
39
    func reloadData(_ fieldGroups: [HyperwalletFieldGroup])
40
    func showFooterViewWithUpdatedSectionData(for sections: [UpdateTransferMethodSectionData])
41
}
42

43
final class UpdateTransferMethodPresenter {
44
    private weak var view: UpdateTransferMethodView?
45
    private let errorTypeApi = "API"
46
    private let updatedConfirmationPageName = "transfer-method:update:transfer-method-updated"
47
    private let pageLink = "update-transfer-method"
48
    private let transferMethodUpdatedGoal = "transfer-method-updated"
49
    static let updateTransferMethodPageGroup = "update-transfer-method"
50
    static let updateTransferMethodPageName = "transfer-method:update:collect-transfer-method-information"
51
    private let hyperwalletInsights: HyperwalletInsightsProtocol
52
    var transferMethodConfiguration: HyperwalletTransferMethodConfiguration?
53
    let transferMethodToken: String
54
    var sectionData = [UpdateTransferMethodSectionData]()
10✔
55

56
    private lazy var transferMethodUpdateConfigurationRepository = {
10✔
57
        TransferMethodRepositoryFactory.shared.transferMethodUpdateConfigurationRepository()
10✔
58
    }()
10✔
59

60
    private lazy var transferMethodRepository = {
8✔
61
        TransferMethodRepositoryFactory.shared.transferMethodRepository()
8✔
62
    }()
8✔
63

64
    init(_ view: UpdateTransferMethodView,
65
         _ transferMethodToken: String,
66
         _ hyperwalletInsights: HyperwalletInsightsProtocol = HyperwalletInsights.shared) {
10✔
67
        self.view = view
10✔
68
        self.transferMethodToken = transferMethodToken
10✔
69
        self.hyperwalletInsights = hyperwalletInsights
10✔
70
    }
10✔
71

72
    func loadTransferMethodUpdateConfigurationFields() {
13✔
73
        view?.showLoading()
13✔
74

13✔
75
        transferMethodUpdateConfigurationRepository
13✔
76
            .getFields(transferMethodToken) { [weak self] (result) in
13✔
77
                guard let strongSelf = self, let view = strongSelf.view else {
13✔
78
                    return
×
79
                }
13✔
80
                view.hideLoading()
13✔
81

13✔
82
                switch result {
13✔
83
                case .failure(let error):
13✔
84
                    view.showError(
3✔
85
                        error,
3✔
86
                        pageName: UpdateTransferMethodPresenter.updateTransferMethodPageName,
3✔
87
                        pageGroup: UpdateTransferMethodPresenter.updateTransferMethodPageGroup) {
3✔
88
                            strongSelf.loadTransferMethodUpdateConfigurationFields()
3✔
89
                    }
3✔
90

13✔
91
                case .success(let result):
13✔
92
                    strongSelf.transferMethodConfiguration = result?.transferMethodUpdateConfiguration()
10✔
93
                    if let fieldGroups = self?.transferMethodConfiguration?.fieldGroups?.nodes {
10✔
94
                        strongSelf.trackUILoadImpression()
10✔
95
                        view.reloadData(fieldGroups)
10✔
96
                    }
13✔
97
                }
13✔
98
            }
13✔
99
    }
13✔
100

101
    func updateTransferMethod() {
17✔
102
        trackConfirmClick()
17✔
103
        guard let view = view, view.areAllUpdatedFieldsValid() else {
17✔
104
            return
×
105
        }
17✔
106

17✔
107
        guard let hyperwalletTransferMethod = buildHyperwalletTransferMethod() else {
17✔
108
            view.showError(title: "error".localized(), message: "transfer_method_not_supported_message".localized())
×
109
            return
×
110
        }
17✔
111
        view.fieldValues().forEach { hyperwalletTransferMethod.setField(key: $0.name, value: $0.value) }
17✔
112

17✔
113
        view.showProcessing()
17✔
114
        transferMethodRepository.updateTransferMethods(hyperwalletTransferMethod) { [weak self] (result) in
17✔
115
            guard let strongSelf = self, let view = strongSelf.view else {
17✔
116
                return
×
117
            }
17✔
118
            switch result {
17✔
119
            case .failure(let error):
17✔
120
                view.dismissProcessing(handler: {
10✔
121
                    strongSelf.errorHandler(for: error)
10✔
122
                })
10✔
123

17✔
124
            case .success(let transferMethodResult):
17✔
125
                view.showConfirmation(handler: {
7✔
126
                    if let transferMethod = transferMethodResult {
7✔
127
                        strongSelf.trackTransferMethodUpdateConfirmationImpression()
7✔
128
                        view.notifyTransferMethodUpdated(transferMethod)
7✔
129
                    }
7✔
130
                })
7✔
131
            }
17✔
132
        }
17✔
133
    }
17✔
134

135
    func prepareSectionForScrolling(_ section: UpdateTransferMethodSectionData,
136
                                    _ row: Int,
137
                                    _ focusWidget: AbstractWidget) {
1✔
138
        section.rowShouldBeScrolledTo = row
1✔
139
        section.fieldToBeFocused = focusWidget
1✔
140
    }
1✔
141

142
    private func errorHandler(for error: HyperwalletErrorType) {
10✔
143
        switch error.group {
10✔
144
        case .business:
10✔
145
            resetErrorMessagesForAllSections()
1✔
146
            if let errors = error.getHyperwalletErrors()?.errorList, errors.isNotEmpty {
1✔
147
                updateFooterContent(errors)
1✔
148
                if errors.contains(where: { $0.fieldName == nil }) {
1✔
149
                    view?.showError(error,
×
150
                                    pageName: UpdateTransferMethodPresenter.updateTransferMethodPageName,
×
151
                                    pageGroup: UpdateTransferMethodPresenter.updateTransferMethodPageGroup,
×
152
                                    nil)
×
153
                }
1✔
154
            }
10✔
155

10✔
156
        default:
10✔
157
            view?.showError(error,
9✔
158
                            pageName: UpdateTransferMethodPresenter.updateTransferMethodPageName,
9✔
159
                            pageGroup: UpdateTransferMethodPresenter.updateTransferMethodPageGroup) { [weak self] in
9✔
160
                                self?.updateTransferMethod()
9✔
161
            }
9✔
162
        }
10✔
163
    }
10✔
164

165
    private func buildHyperwalletTransferMethod()
166
        -> HyperwalletTransferMethod? {
17✔
167
        let transferMethodTypeCode = transferMethodConfiguration?.transferMethodType ?? ""
17✔
168
        switch transferMethodTypeCode {
17✔
169
        case HyperwalletTransferMethod.TransferMethodType.bankAccount.rawValue,
17✔
170
             HyperwalletTransferMethod.TransferMethodType.wireAccount.rawValue:
13✔
171
            let bankAccount = HyperwalletBankAccount.Builder(token: transferMethodToken)
13✔
172
                .build()
13✔
173
            return bankAccount
13✔
174

17✔
175
        case HyperwalletTransferMethod.TransferMethodType.bankCard.rawValue:
17✔
176
            let bankCard = HyperwalletBankCard.Builder(token: transferMethodToken)
1✔
177
                .build()
1✔
178
            return bankCard
1✔
179

17✔
180
        case HyperwalletTransferMethod.TransferMethodType.payPalAccount.rawValue:
17✔
181
            let payPal = HyperwalletPayPalAccount.Builder(token: transferMethodToken)
1✔
182
                .build()
1✔
183
            return payPal
1✔
184

17✔
185
        case HyperwalletTransferMethod.TransferMethodType.venmoAccount.rawValue:
17✔
186
            let venmo = HyperwalletVenmoAccount.Builder(token: transferMethodToken)
1✔
187
                .build()
1✔
188
            return venmo
1✔
189

17✔
190
        case HyperwalletTransferMethod.TransferMethodType.paperCheck.rawValue:
17✔
191
            let paperCheck = HyperwalletPaperCheck.Builder(token: transferMethodToken)
1✔
192
                .build()
1✔
193
            return paperCheck
1✔
194

17✔
195
        default:
17✔
196
            return nil
×
197
        }
17✔
198
    }
17✔
199

200
    private func updateFooterContent(_ errors: [HyperwalletError]) {
1✔
201
        let errorsWithFieldName = errors.filter({ $0.fieldName != nil })
1✔
202

1✔
203
        if errorsWithFieldName.isNotEmpty {
1✔
204
            if let section = sectionData
1✔
205
                .first(where: { section in widgetsContainError(for: section, errors).isNotEmpty }) {
1✔
206
                section.containsFocusedField = true
1✔
207
            }
1✔
208

1✔
209
            for section in sectionData {
3✔
210
                updateSectionData(for: section, errorsWithFieldName)
3✔
211
            }
3✔
212
        }
1✔
213

1✔
214
        view?.showFooterViewWithUpdatedSectionData(for: sectionData.reversed())
1✔
215
    }
1✔
216

217
    private func updateSectionData(for section: UpdateTransferMethodSectionData,
218
                                   _ errorsWithFieldName: [HyperwalletError]) {
3✔
219
        let errorWidgets = widgetsContainError(for: section, errorsWithFieldName)
3✔
220
        if let focusWidget = errorWidgets.first, let row = section.cells.firstIndex(of: focusWidget) {
3✔
221
            prepareSectionForScrolling(section, row, focusWidget)
1✔
222
            prepareErrorMessage(section, errorsWithFieldName, errorWidgets)
1✔
223
        }
3✔
224
    }
3✔
225

226
    private func prepareErrorMessage(_ section: UpdateTransferMethodSectionData,
227
                                     _ errors: [HyperwalletError],
228
                                     _ errorWidgets: [AbstractWidget]) {
1✔
229
        var errorMessages = [String]()
1✔
230
        for widget in errorWidgets {
1✔
231
            // get the errorMessage by widget name and update widget UI
1✔
232
            if let error = errors.first(where: { error in widget.name() == error.fieldName }) {
1✔
233
                trackError(errorMessage: error.message,
1✔
234
                           errorCode: error.code,
1✔
235
                           errorType: errorTypeApi,
1✔
236
                           fieldName: widget.name())
1✔
237
                widget.showError()
1✔
238
                errorMessages.append(error.message)
1✔
239
            }
1✔
240
        }
1✔
241
        section.errorMessage = errorMessages.joined(separator: "\n")
1✔
242
    }
1✔
243

244
    private func trackError(errorMessage: String,
245
                            errorCode: String,
246
                            errorType: String,
247
                            fieldName: String) {
1✔
248
        let errorInfo = ErrorInfoBuilder(type: errorType, message: errorMessage)
1✔
249
            .fieldName(fieldName)
1✔
250
            .code(errorCode)
1✔
251
            .build()
1✔
252
        hyperwalletInsights.trackError(pageName: UpdateTransferMethodPresenter.updateTransferMethodPageName,
1✔
253
                                       pageGroup: UpdateTransferMethodPresenter.updateTransferMethodPageGroup,
1✔
254
                                       errorInfo: errorInfo)
1✔
255
    }
1✔
256

257
    private func widgetsContainError(for section: UpdateTransferMethodSectionData,
258
                                     _ errors: [HyperwalletError]) -> [AbstractWidget] {
4✔
259
        return allWidgets(of: section).filter { widget in errors
15✔
260
            .contains(where: { error in widget.name() == error.fieldName })
15✔
261
        }
15✔
262
    }
4✔
263

264
    private func allWidgets(of section: UpdateTransferMethodSectionData) -> [AbstractWidget] {
4✔
265
        return section.cells.compactMap { $0 as? AbstractWidget }
15✔
266
    }
4✔
267

268
    func resetErrorMessagesForAllSections() {
1✔
269
        sectionData.forEach { $0.errorMessage = nil }
3✔
270
    }
1✔
271

272
    private func trackUILoadImpression () {
10✔
273
        hyperwalletInsights.trackImpression(pageName: UpdateTransferMethodPresenter.updateTransferMethodPageName,
10✔
274
                                            pageGroup: UpdateTransferMethodPresenter.updateTransferMethodPageGroup,
10✔
275
                                            params: insightsParam())
10✔
276
    }
10✔
277

278
    private func trackConfirmClick() {
17✔
279
        hyperwalletInsights.trackClick(
17✔
280
            pageName: UpdateTransferMethodPresenter.updateTransferMethodPageName,
17✔
281
            pageGroup: UpdateTransferMethodPresenter.updateTransferMethodPageGroup,
17✔
282
            link: pageLink,
17✔
283
            params: insightsParam())
17✔
284
    }
17✔
285

286
    private func trackTransferMethodUpdateConfirmationImpression() {
7✔
287
        hyperwalletInsights.trackImpression(pageName: updatedConfirmationPageName,
7✔
288
                                            pageGroup: UpdateTransferMethodPresenter.updateTransferMethodPageGroup,
7✔
289
                                            params: [
7✔
290
                                                InsightsTags.country: transferMethodConfiguration?.country ?? "",
7✔
291
                                                InsightsTags.currency: transferMethodConfiguration?.currency ?? "",
7✔
292
                                                InsightsTags.transferMethodType: transferMethodConfiguration?
7✔
293
                                                    .transferMethodType ?? "",
7✔
294
                                                InsightsTags.profileType: transferMethodConfiguration?.profile ?? "",
7✔
295
                                                InsightsTags.goal: transferMethodUpdatedGoal
7✔
296
                                            ])
7✔
297
    }
7✔
298

299
    private func insightsParam () -> [String: String] {
27✔
300
        return [
27✔
301
            InsightsTags.country: transferMethodConfiguration?.country ?? "",
27✔
302
            InsightsTags.currency: transferMethodConfiguration?.currency ?? "",
27✔
303
            InsightsTags.transferMethodType: transferMethodConfiguration?.transferMethodType ?? "",
27✔
304
            InsightsTags.profileType: transferMethodConfiguration?.profile ?? ""
27✔
305
        ]
27✔
306
    }
27✔
307
}
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