Skip to content

Commit cf23f11

Browse files
feat(ad-hoc): general improvements (#497)
* Allow selecting 3DS within dynamic checkout example. * Define dedicated code for security errors. * Prioritize token instead of error code when parsing APM response. * Make nAPM images optional.
1 parent 06c0b83 commit cf23f11

13 files changed

Lines changed: 111 additions & 31 deletions

File tree

Example/Example/Resources/Localizable.xcstrings

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -361,6 +361,16 @@
361361
}
362362
}
363363
},
364+
"dynamic-checkout.3ds-service" : {
365+
"localizations" : {
366+
"en" : {
367+
"stringUnit" : {
368+
"state" : "translated",
369+
"value" : "3DS Service"
370+
}
371+
}
372+
}
373+
},
364374
"dynamic-checkout.error-message" : {
365375
"localizations" : {
366376
"en" : {

Example/Example/Sources/UI/Modules/CardPayment/ViewModel/CardPaymentViewModel.swift

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,12 @@ final class CardPaymentViewModel: ObservableObject {
1818

1919
init(invoicesService: POInvoicesService) {
2020
self.invoicesService = invoicesService
21-
commonInit()
2221
}
2322

2423
// MARK: -
2524

2625
@Published
27-
var state: CardPaymentViewModelState! // swiftlint:disable:this implicitly_unwrapped_optional
26+
var state = CardPaymentViewModelState()
2827

2928
func pay() {
3029
state.message = nil
@@ -37,13 +36,6 @@ final class CardPaymentViewModel: ObservableObject {
3736

3837
// MARK: - Private Methods
3938

40-
private func commonInit() {
41-
state = .init(
42-
authenticationService: .init(sources: [.test, .checkout, .netcetera], id: \.self, selection: .netcetera),
43-
cardTokenization: nil
44-
)
45-
}
46-
4739
private func setCardTokenizationItem() {
4840
let configuration = POCardTokenizationConfiguration(
4941
cardholderName: nil,

Example/Example/Sources/UI/Modules/CardPayment/ViewModel/CardPaymentViewModelState.swift

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,9 @@ struct CardPaymentViewModelState {
4141
var invoice = InvoiceViewModel()
4242

4343
/// 3DS service.
44-
var authenticationService: PickerData<AuthenticationService, AuthenticationService>
44+
var authenticationService = PickerData<AuthenticationService, AuthenticationService>(
45+
sources: [.test, .checkout, .netcetera], id: \.self, selection: .netcetera
46+
)
4547

4648
/// Card tokenization.
4749
var cardTokenization: CardTokenization?

Example/Example/Sources/UI/Modules/DynamicCheckout/Symbols/LocalizedStringResource+DynamicCheckout.swift

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@ extension LocalizedStringResource {
1414
/// Title.
1515
static let title = LocalizedStringResource("dynamic-checkout.title")
1616

17+
/// 3DS service.
18+
static let threeDSService = LocalizedStringResource("dynamic-checkout.3ds-service")
19+
1720
/// Continue button.
1821
static let pay = LocalizedStringResource("dynamic-checkout.pay")
1922

Example/Example/Sources/UI/Modules/DynamicCheckout/View/DynamicCheckoutView.swift

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,13 @@ struct DynamicCheckoutView: View {
1616
MessageView(viewModel: viewModel)
1717
}
1818
InvoiceView(viewModel: $viewModel.state.invoice)
19+
Section {
20+
Picker(data: $viewModel.state.authenticationService) { service in
21+
Text(service.rawValue.capitalized)
22+
} label: {
23+
Text(.DynamicCheckout.threeDSService)
24+
}
25+
}
1926
Button(String(localized: .DynamicCheckout.pay)) {
2027
viewModel.pay()
2128
}

Example/Example/Sources/UI/Modules/DynamicCheckout/ViewModel/DynamicCheckoutViewModel.swift

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@ import PassKit
99
import SwiftUI
1010
@_spi(PO) import ProcessOut
1111
@_spi(PO) import ProcessOutUI
12+
import ProcessOutCheckout3DS
13+
import ProcessOutNetcetera3DS
14+
import Checkout3DS
1215

1316
@MainActor
1417
final class DynamicCheckoutViewModel: ObservableObject {
@@ -112,7 +115,16 @@ extension DynamicCheckoutViewModel: PODynamicCheckoutDelegate {
112115
willAuthorizeInvoiceWith request: inout POInvoiceAuthorizationRequest,
113116
using paymentMethod: PODynamicCheckoutPaymentMethod
114117
) async -> any PO3DS2Service {
115-
POTest3DSService()
118+
switch state.authenticationService.selection {
119+
case .test:
120+
return POTest3DSService()
121+
case .checkout:
122+
let delegate = DefaultCheckout3DSDelegate()
123+
return POCheckout3DSService(delegate: delegate, environment: .sandbox)
124+
case .netcetera:
125+
let configuration = PONetcetera3DS2ServiceConfiguration(returnUrl: Constants.returnUrl)
126+
return PONetcetera3DS2Service(configuration: configuration)
127+
}
116128
}
117129

118130
func dynamicCheckout(willAuthorizeInvoiceWith request: PKPaymentRequest) async {

Example/Example/Sources/UI/Modules/DynamicCheckout/ViewModel/DynamicCheckoutViewModelState.swift

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,18 @@ import ProcessOut
1111

1212
struct DynamicCheckoutViewModelState {
1313

14+
enum AuthenticationService: String, Hashable {
15+
16+
/// Test service.
17+
case test
18+
19+
/// Checkout service.
20+
case checkout
21+
22+
/// Netcetera 3DS SDK.
23+
case netcetera
24+
}
25+
1426
struct DynamicCheckout: Identifiable {
1527

1628
let id: String
@@ -28,6 +40,11 @@ struct DynamicCheckoutViewModelState {
2840
/// Invoice details.
2941
var invoice = InvoiceViewModel()
3042

43+
/// 3DS service.
44+
var authenticationService = PickerData<AuthenticationService, AuthenticationService>(
45+
sources: [.test, .checkout, .netcetera], id: \.self, selection: .netcetera
46+
)
47+
3148
/// Dynamic checkout.
3249
var dynamicCheckout: DynamicCheckout?
3350

Sources/ProcessOut/Sources/Connectors/Http/Implementations/UrlSession/UrlSessionHttpConnector.swift

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,9 +87,22 @@ final class UrlSessionHttpConnector: HttpConnector {
8787
private func convertToFailure(urlError error: Error) -> Failure {
8888
let code: Failure.Code
8989
switch error {
90+
case URLError.appTransportSecurityRequiresSecureConnection,
91+
URLError.secureConnectionFailed,
92+
URLError.serverCertificateHasBadDate,
93+
URLError.serverCertificateHasUnknownRoot,
94+
URLError.serverCertificateNotYetValid,
95+
URLError.serverCertificateUntrusted,
96+
URLError.clientCertificateRejected,
97+
URLError.clientCertificateRequired:
98+
code = .security
9099
case URLError.cancelled:
91100
code = .cancelled
92-
case URLError.notConnectedToInternet, URLError.networkConnectionLost:
101+
case URLError.notConnectedToInternet,
102+
URLError.networkConnectionLost,
103+
URLError.internationalRoamingOff,
104+
URLError.dataNotAllowed,
105+
URLError.callIsActive:
93106
code = .networkUnreachable
94107
case URLError.timedOut:
95108
code = .timeout

Sources/ProcessOut/Sources/Connectors/Http/Models/HttpConnectorFailure.swift

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,9 @@ struct HttpConnectorFailure: Error, Sendable {
2727
/// Cancellation error.
2828
case cancelled
2929

30+
/// An attempt to establish a secure connection failed.
31+
case security
32+
3033
/// Internal error.
3134
case `internal`
3235
}

Sources/ProcessOut/Sources/Repositories/Shared/Decorators/HttpConnectorError/FailureMapper/DefaultHttpConnectorFailureMapper.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,10 @@ final class DefaultHttpConnectorFailureMapper: HttpConnectorFailureMapper {
3434
message = "Request was cancelled."
3535
code = .Mobile.cancelled
3636
invalidFields = nil
37+
case .security:
38+
message = "An attempt to establish a secure connection failed."
39+
code = .Mobile.connectionSecurity
40+
invalidFields = nil
3741
case let .server(error, _):
3842
message = error.message
3943
code = .init(rawValue: error.errorType)

0 commit comments

Comments
 (0)