Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions Sources/ProcessOut/Sources/Core/Utils/Task+Cancellation.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
//
// Task+Cancellation.swift
// ProcessOut
//
// Created by Andrii Vysotskyi on 28.09.2026.
//

extension Task where Success == Never, Failure == Never {

/// Throws cancellation failure if the current task has been cancelled.
@_spi(PO)
public static func poCheckCancellation() throws(POFailure) {
guard Task.isCancelled else {
return
}
throw POFailure(
message: "Task was cancelled.",
code: .Mobile.cancelled,
underlyingError: _Concurrency.CancellationError()
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -50,19 +50,24 @@ public struct POInvoiceCreationRequest: Encodable, Sendable {
/// Invoice details.
public let details: [Detail]

/// Additional metadata attached to the invoice.
public let metadata: [String: String]?

public init(
name: String,
amount: Decimal,
currency: String,
returnUrl: URL? = nil,
customerId: String? = nil,
details: [Detail] = []
details: [Detail] = [],
metadata: [String: String]? = nil
) {
self.name = name
self._amount = .init(value: amount)
self.currency = currency
self.returnUrl = returnUrl
self.customerId = customerId
self.details = details
self.metadata = metadata
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -368,6 +368,16 @@ extension POFailureCode {

/// The redirect result is not support and can't be handled.
public static let redirectResultInvalid = POFailureCode(rawValue: "request.validation.redirect-result-invalid")

/// The redirect result is not expected, for example because payment has already advanced past the redirect.
public static let redirectResultUnexpected = POFailureCode(
rawValue: "request.validation.redirect-result-unexpected"
)

/// The redirect result is not expected for the step payment is currently at.
public static let redirectResultUnexpectedForStep = POFailureCode(
rawValue: "request.validation.redirect-result-unexpected-for-step"
)
}

public enum Request {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -145,8 +145,19 @@ public struct PONativeAlternativePaymentConfiguration {

/// Enables headless mode.
///
/// The web redirect will be handled directly when it's the first step in the flow,
/// and if it's the only required step, it will complete the flow without starting the bottom sheet.
/// When enabled, a redirect returned as the first step of the flow is performed immediately,
/// skipping the confirmation configured with ``redirectButton``. Redirects required at later
/// steps (for example, after parameters are submitted) still respect ``redirectButton``.
///
/// - Note: When ``redirectButton`` is `nil` (the default), redirects never require confirmation,
/// so this option has no effect.
///
/// - Note: For web redirects the flow waits for the customer to return, so if the redirect is
/// the only required step the payment may complete before the component's `start()` returns,
/// without presenting the component's own UI. The redirect itself is still presented in a system
/// web authentication session, which requires a foreground-active window. For deep link redirects
/// the flow continues as soon as the link is opened, so `start()` usually returns while the payment
/// is still pending.
public let enableHeadlessMode: Bool

/// Redirect confirmation button configuration. To remove button use `nil`, this is default behaviour.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ final class DefaultNativeAlternativePaymentServiceAdapter: NativeAlternativePaym
while: { result in
switch result {
case let .success(response):
return response.state != .success
return response.state == .pending // Any other state is handled by caller
case let .failure(failure as POFailure):
let retriableCodes: [POFailureCode] = [
.Mobile.networkUnreachable, .Mobile.timeout, .Mobile.internal
Expand Down
Loading
Loading