Skip to content

Commit 7f6e2ee

Browse files
feat(ad-hoc): support nAPM deep links (#506)
1 parent d2b7eff commit 7f6e2ee

7 files changed

Lines changed: 104 additions & 15 deletions

File tree

Sources/ProcessOut/Sources/Repositories/CustomerTokens/Requests/PONativeAlternativePaymentTokenizationRequestV2.swift

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,9 @@ public struct PONativeAlternativePaymentTokenizationRequestV2: Sendable, Encodab
1919
/// Payment request parameters.
2020
public let submitData: PONativeAlternativePaymentSubmitDataV2?
2121

22+
/// Redirect result.
23+
public let redirect: PONativeAlternativePaymentRedirectResultV2?
24+
2225
/// Customer's locale identifier override.
2326
@POExcludedEncodable
2427
public private(set) var localeIdentifier: String?
@@ -28,12 +31,14 @@ public struct PONativeAlternativePaymentTokenizationRequestV2: Sendable, Encodab
2831
customerTokenId: String,
2932
gatewayConfigurationId: String,
3033
submitData: PONativeAlternativePaymentSubmitDataV2? = nil,
34+
redirect: PONativeAlternativePaymentRedirectResultV2? = nil,
3135
localeIdentifier: String? = nil
3236
) {
3337
self.customerId = customerId
3438
self.customerTokenId = customerTokenId
3539
self.gatewayConfigurationId = gatewayConfigurationId
3640
self.submitData = submitData
41+
self.redirect = redirect
3742
self.localeIdentifier = localeIdentifier
3843
}
3944
}

Sources/ProcessOut/Sources/Repositories/Invoices/Requests/AlternativePaymentV2/PONativeAlternativePaymentAuthorizationRequestV2.swift

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,9 @@ public struct PONativeAlternativePaymentAuthorizationRequestV2: Sendable, Encoda
1919
/// Payment request parameters.
2020
public let submitData: PONativeAlternativePaymentSubmitDataV2?
2121

22+
/// Redirect result.
23+
public let redirect: PONativeAlternativePaymentRedirectResultV2?
24+
2225
/// Customer's locale identifier override.
2326
@POExcludedEncodable
2427
public private(set) var localeIdentifier: String?
@@ -27,13 +30,15 @@ public struct PONativeAlternativePaymentAuthorizationRequestV2: Sendable, Encoda
2730
invoiceId: String,
2831
gatewayConfigurationId: String,
2932
source: String? = nil,
30-
submitData: PONativeAlternativePaymentSubmitDataV2?,
33+
submitData: PONativeAlternativePaymentSubmitDataV2? = nil,
34+
redirect: PONativeAlternativePaymentRedirectResultV2? = nil,
3135
localeIdentifier: String? = nil
3236
) {
3337
self.invoiceId = invoiceId
3438
self.gatewayConfigurationId = gatewayConfigurationId
3539
self.source = source
3640
self.submitData = submitData
41+
self.redirect = redirect
3742
self.localeIdentifier = localeIdentifier
3843
}
3944
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
//
2+
// PONativeAlternativePaymentRedirectResultV2.swift
3+
// ProcessOut
4+
//
5+
// Created by Andrii Vysotskyi on 23.12.2025.
6+
//
7+
8+
public struct PONativeAlternativePaymentRedirectResultV2: Sendable, Encodable {
9+
10+
public init(success: Bool) {
11+
self.success = success
12+
}
13+
14+
/// indicates whether customer was redirected successfully.
15+
public let success: Bool
16+
}

Sources/ProcessOut/Sources/Repositories/Shared/Responses/AlternativePaymentV2/PONativeAlternativePaymentRedirectV2.swift

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,43 @@ import Foundation
1010
/// Redirect details.
1111
public struct PONativeAlternativePaymentRedirectV2: Decodable, Sendable {
1212

13+
public struct RedirectType: Hashable, RawRepresentable, Sendable {
14+
15+
public init(rawValue: String) {
16+
self.rawValue = rawValue
17+
}
18+
19+
/// Raw value.
20+
public let rawValue: String
21+
}
22+
1323
/// Destination URL.
1424
public let url: URL
1525

1626
/// Display hint describing redirect purpose.
1727
public let hint: String
28+
29+
/// Redirect type.
30+
public let type: RedirectType
31+
32+
/// Boolean value indicating whether backend expects redirect confirmation after customer
33+
/// is redirected to url.
34+
public let confirmationRequired: Bool
35+
}
36+
37+
extension PONativeAlternativePaymentRedirectV2.RedirectType: Decodable {
38+
39+
public init(from decoder: any Decoder) throws {
40+
let container = try decoder.singleValueContainer()
41+
rawValue = try container.decode(String.self)
42+
}
43+
}
44+
45+
extension PONativeAlternativePaymentRedirectV2.RedirectType {
46+
47+
/// Web redirect.
48+
public static let web = Self(rawValue: "web")
49+
50+
/// Deep link redirect.
51+
public static let deepLink = Self(rawValue: "deep_link")
1852
}

Sources/ProcessOutUI/Sources/Modules/NativeAlternativePayment/Interactor/Adapter/DefaultNativeAlternativePaymentServiceAdapter.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ final class DefaultNativeAlternativePaymentServiceAdapter: NativeAlternativePaym
3232
gatewayConfigurationId: flow.gatewayConfigurationId,
3333
source: flow.customerTokenId,
3434
submitData: request.submitData,
35+
redirect: request.redirect,
3536
localeIdentifier: request.localeIdentifier
3637
)
3738
let authorizationResponse = try await invoicesService.authorizeInvoice(request: authorizationRequest)
@@ -42,6 +43,7 @@ final class DefaultNativeAlternativePaymentServiceAdapter: NativeAlternativePaym
4243
customerTokenId: flow.customerTokenId,
4344
gatewayConfigurationId: flow.gatewayConfigurationId,
4445
submitData: request.submitData,
46+
redirect: request.redirect,
4547
localeIdentifier: request.localeIdentifier
4648
)
4749
let tokenizationResponse = try await tokensService.tokenize(request: tokenizationRequest)

Sources/ProcessOutUI/Sources/Modules/NativeAlternativePayment/Interactor/Adapter/NativeAlternativePaymentServiceAdapterRequest.swift

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,12 @@ struct NativeAlternativePaymentServiceAdapterRequest {
1212
init(
1313
flow: PONativeAlternativePaymentConfiguration.Flow,
1414
submitData: PONativeAlternativePaymentSubmitDataV2? = nil,
15+
redirect: PONativeAlternativePaymentRedirectResultV2? = nil,
1516
localeIdentifier: String?
1617
) {
1718
self.flow = flow
1819
self.submitData = submitData
20+
self.redirect = redirect
1921
self.localeIdentifier = localeIdentifier
2022
}
2123

@@ -25,6 +27,9 @@ struct NativeAlternativePaymentServiceAdapterRequest {
2527
/// Data to submit if any.
2628
let submitData: PONativeAlternativePaymentSubmitDataV2?
2729

30+
/// Redirect result.
31+
let redirect: PONativeAlternativePaymentRedirectResultV2?
32+
2833
/// Customer's locale identifier override.
2934
let localeIdentifier: String?
3035
}

Sources/ProcessOutUI/Sources/Modules/NativeAlternativePayment/Interactor/NativeAlternativePaymentDefaultInteractor.swift

Lines changed: 36 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -130,15 +130,25 @@ final class NativeAlternativePaymentDefaultInteractor:
130130
}
131131
let task = Task {
132132
do {
133-
let authenticationRequest = POAlternativePaymentAuthenticationRequest(
134-
url: currentState.redirect.url,
135-
callback: configuration.redirect.callback,
136-
prefersEphemeralSession: configuration.redirect.prefersEphemeralSession
137-
)
138-
_ = try await alternativePaymentsService.authenticate(request: authenticationRequest)
133+
let didOpenUrl: Bool
134+
switch currentState.redirect.type {
135+
case .deepLink:
136+
didOpenUrl = await UIApplication.shared.open(currentState.redirect.url)
137+
case .web:
138+
let authenticationRequest = POAlternativePaymentAuthenticationRequest(
139+
url: currentState.redirect.url,
140+
callback: configuration.redirect.callback,
141+
prefersEphemeralSession: configuration.redirect.prefersEphemeralSession
142+
)
143+
_ = try await alternativePaymentsService.authenticate(request: authenticationRequest)
144+
didOpenUrl = true
145+
default:
146+
throw POFailure(errorDescription: "Unknown redirect type.", code: .Mobile.internal)
147+
}
139148
let response = try await serviceAdapter.continuePayment(
140149
with: .init(
141150
flow: configuration.flow,
151+
redirect: currentState.redirect.confirmationRequired ? .init(success: didOpenUrl) : nil,
142152
localeIdentifier: configuration.localization.localeOverride?.identifier
143153
)
144154
)
@@ -214,15 +224,27 @@ final class NativeAlternativePaymentDefaultInteractor:
214224
logger.error("Attempted to handle headless redirect while not in starting state. Ignoring.")
215225
return
216226
}
217-
let authenticationRequest = POAlternativePaymentAuthenticationRequest(
218-
url: redirect.url,
219-
callback: configuration.redirect.callback,
220-
prefersEphemeralSession: configuration.redirect.prefersEphemeralSession
221-
)
222-
_ = try await alternativePaymentsService.authenticate(request: authenticationRequest)
223-
let localeIdentifier = configuration.localization.localeOverride?.identifier
227+
let didOpenUrl: Bool
228+
switch redirect.type {
229+
case .deepLink:
230+
didOpenUrl = await UIApplication.shared.open(redirect.url)
231+
case .web:
232+
let authenticationRequest = POAlternativePaymentAuthenticationRequest(
233+
url: redirect.url,
234+
callback: configuration.redirect.callback,
235+
prefersEphemeralSession: configuration.redirect.prefersEphemeralSession
236+
)
237+
_ = try await alternativePaymentsService.authenticate(request: authenticationRequest)
238+
didOpenUrl = true
239+
default:
240+
throw POFailure(errorDescription: "Unknown redirect type.", code: .Mobile.internal)
241+
}
224242
let response = try await serviceAdapter.continuePayment(
225-
with: .init(flow: configuration.flow, localeIdentifier: localeIdentifier)
243+
with: .init(
244+
flow: configuration.flow,
245+
redirect: redirect.confirmationRequired ? .init(success: didOpenUrl) : nil,
246+
localeIdentifier: configuration.localization.localeOverride?.identifier
247+
)
226248
)
227249
try await setState(with: response)
228250
}

0 commit comments

Comments
 (0)