Learn how to configure the payout submission component for iOS.
The PayoutSubmissionComponent provides a submit button to execute payouts for returning users who have already linked their PayPal accounts. This component handles payout validation, merchant approval, and transaction execution. Use this component alongside the PayoutAmountComponent and PayPalPayoutReceiverComponent to create a complete direct payout experience.
The payout submission component requires minimal component-level configuration, but depends on PayPal wallet credentials in CheckoutConfig:
// Required: Configure PayPal wallet in CheckoutConfig
let paypalConfig = PayPalConfig(
payout: PayPalPayoutConfig(
paypalWallet: PayPalWallet(
email: "user@example.com",
payerId: "PAYERID123ABC",
proceedPayoutWithSdk: true
)
)
)
let checkoutConfig = CheckoutConfig(
environment: .test,
session: sessionData,
transactionData: transactionData,
merchantShopperId: "user-123",
ownerId: "merchant-456",
paypalConfig: paypalConfig
)
let pxpCheckout = try PxpCheckout.initialize(config: checkoutConfig)
// Then create the component
let config = PayoutSubmissionComponentConfig()
let component = try pxpCheckout.create(.payoutSubmission, componentConfig: config)The paypalConfig.payout.paypalWallet.email and paypalConfig.payout.paypalWallet.payerId fields are required in CheckoutConfig before create(.payoutSubmission, ...) can succeed. These credentials should be stored from a previous PayPal OAuth flow.
Customise the button text:
let config = PayoutSubmissionComponentConfig(
submitText: "Withdraw to PayPal",
submitAccessibilityLabel: "Withdraw funds to PayPal account"
)
let component = try pxpCheckout.create(.payoutSubmission, componentConfig: config)| Property | Description |
|---|---|
submitTextString? | The custom text for the submit button. Defaults to "Withdraw with PayPal". |
submitAccessibilityLabelString? | The accessibility label for the button. Defaults to "Withdraw with PayPal". |
Add callbacks to handle the payout flow:
let config = PayoutSubmissionComponentConfig(
submitText: "Withdraw Now",
onClick: {
print("Payout button clicked")
},
onPrePayoutSubmit: {
let approved = await showApprovalDialog()
guard approved else { return nil }
return PrePayoutSubmitResult(isApproved: true)
},
onPostPayout: { result in
print("Payout completed: \(result.merchantTransactionId)")
},
onError: { error in
if let sdkError = error as? BaseSdkException {
print("Error: \(sdkError.errorMessage)")
}
}
)| Property | Description |
|---|---|
onClick(() -> Void)? | Called when the button is clicked, before payout execution. |
onPrePayoutSubmit(() async -> PrePayoutSubmitResult?)? | Called for merchant approval before payout execution. Only used when proceedPayoutWithSdk is true. Return nil to cancel. |
onPostPayout((MerchantSubmitResult) -> Void)? | Called after the payout completes successfully. |
onError((Error) -> Void)? | Called when an error occurs. Cast to BaseSdkException to access error details. |
For more complex implementations, configure styling and callbacks:
let config = PayoutSubmissionComponentConfig(
recipientType: .email,
wallet: .paypal,
submitText: "Confirm Withdrawal",
submitAccessibilityLabel: "Confirm withdrawal to PayPal",
// Custom button styling
styles: ButtonStateStyles(
base: FieldStyle(
color: .white,
fontSize: 16,
backgroundColor: Color(red: 0.0, green: 0.44, blue: 0.73),
borderColor: Color(red: 0.0, green: 0.44, blue: 0.73),
borderWidth: 0,
cornerRadius: 8,
padding: EdgeInsets(top: 14, leading: 24, bottom: 14, trailing: 24),
opacity: 1.0
),
disabled: FieldStyle(
color: Color(.systemGray),
backgroundColor: Color(.systemGray5),
opacity: 0.6
),
loading: FieldStyle(
color: .white,
backgroundColor: Color(red: 0.0, green: 0.37, blue: 0.65),
opacity: 0.8
)
),
onClick: { },
onPrePayoutSubmit: {
// Perform validation and approval logic
return PrePayoutSubmitResult(isApproved: true)
},
onPostPayout: { result in },
onError: { error in }
)| Property | Description |
|---|---|
recipientTypePayoutRecipientType | The recipient identifier type used in the SDK configuration. Note: The current SDK implementation validates .email in the PayPal wallet config, but internally uses the payerId for payout execution. Defaults to .email. |
walletPayPalPayoutWalletType | The wallet type. Currently only .paypal is supported. Any other value throws PayoutWalletInvalidException. Defaults to .paypal. |
submitTextString? | The custom text for the submit button. |
submitAccessibilityLabelString? | The accessibility label for the button. |
stylesButtonStateStyles? | Custom button styles for different states. |
The payout submission component provides event handlers to manage the payout flow:
let config = PayoutSubmissionComponentConfig(
submitText: "Withdraw",
onClick: { },
onPrePayoutSubmit: {
return PrePayoutSubmitResult(isApproved: true)
},
onPostPayout: { result in },
onError: { error in }
)| Callback | Description |
|---|---|
onClick: (() -> Void)? | Called when the button is clicked, before payout execution begins. |
onPrePayoutSubmit: (() async -> PrePayoutSubmitResult?)? | Called for merchant approval before payout execution. Required for payout execution when proceedPayoutWithSdk is true. Return nil or isApproved: false to prevent payout execution. |
onPostPayout: ((MerchantSubmitResult) -> Void)? | Called when payout completes successfully. Receives transaction IDs. |
onError: ((BaseSdkException) -> Void)? | Called when an error occurs during payout execution. Note: Most validation errors occur during component creation, not during button click. |
Payout execution only happens when all three conditions are met:
paypalConfig.payout.paypalWallet.proceedPayoutWithSdkis set totrue.onPrePayoutSubmitcallback is provided.onPrePayoutSubmitreturnsPrePayoutSubmitResult(isApproved: true).
If any condition isn't met, the payout won't execute.
The onCancel callback is currently not invoked by the SDK implementation.
Triggered when the user taps the submit button:
config.onClick = {
print("Payout button clicked")
// Track analytics
Analytics.track("payout_submit_clicked", properties: [
"userId": currentUser.id,
"amount": getCurrentPayoutAmount()
])
}Triggered before payout execution when proceedPayoutWithSdk is true, allowing you to implement approval flows:
config.onPrePayoutSubmit = {
// Perform custom validation
let isValid = await validatePayoutEligibility()
guard isValid else {
showError("You are not eligible for this payout")
return nil
}
// Show approval dialog
let approved = await showApprovalDialog(
amount: getCurrentPayoutAmount(),
recipient: getStoredPayPalEmail()
)
guard approved else {
Analytics.track("payout_approval_rejected")
return nil
}
Analytics.track("payout_approval_granted")
return PrePayoutSubmitResult(isApproved: true)
}Payout execution only happens when all three conditions are met:
paypalConfig.payout.paypalWallet.proceedPayoutWithSdkis set totrue.- This
onPrePayoutSubmitcallback is provided. - This callback returns
PrePayoutSubmitResult(isApproved: true).
If this callback isn't provided, returns nil, or returns isApproved: false, the payout won't execute.
| Property | Description |
|---|---|
isApprovedBoolrequired | Whether the payout is approved. |
Triggered after the payout transaction completes successfully:
config.onPostPayout = { result in
print("Payout completed!")
print("Merchant TX ID: \(result.merchantTransactionId)")
print("System TX ID: \(result.systemTransactionId)")
// Update records
updatePayoutRecord(
merchantTxId: result.merchantTransactionId,
systemTxId: result.systemTransactionId,
status: "completed"
)
// Update user balance
updateUserBalance(deduct: getCurrentPayoutAmount())
// Show success message
showSuccessMessage("Your payout has been sent!")
// Track analytics
Analytics.track("payout_completed", properties: [
"transactionId": result.merchantTransactionId,
"amount": getCurrentPayoutAmount()
])
}| Property | Description |
|---|---|
result.merchantTransactionIdStringrequired | Your unique transaction identifier. |
result.systemTransactionIdStringrequired | The PXP system transaction ID. |
Triggered when an error occurs during payout execution:
Most payout validation errors (receiver missing, invalid wallet configuration, etc.) are thrown during component creation via pxpCheckout.create(.payoutSubmission, ...) and must be caught with try-catch. The onError callback is primarily for errors that occur during the actual payout transaction execution.
config.onError = { error in
guard let sdkError = error as? BaseSdkException else {
print("Unknown error: \(error.localizedDescription)")
return
}
print("Payout error: \(sdkError.errorMessage)")
print("Error code: \(sdkError.errorCode)")
// Handle specific errors
let errorMessage: String
switch sdkError.errorCode {
case "SDK0816":
errorMessage = "PayPal email address is too long."
case "SDK0817", "SDK0818":
errorMessage = "Invalid payer ID. Please re-link your PayPal account."
case "SDK0819":
errorMessage = "Payout transaction failed. Please try again."
default:
// Backend/API/HTTP error codes may also appear here
errorMessage = "Payout failed. Please try again."
}
Task { @MainActor in
showErrorDialog(title: "Payout Error", message: errorMessage)
}
// Track error
Analytics.track("payout_error", properties: [
"errorCode": sdkError.errorCode,
"errorMessage": sdkError.errorMessage
])
}The payout submission component renders with default button styles:
// Base state (default)
FieldStyle(
color: Color(red: 9/255, green: 9/255, blue: 12/255),
fontSize: 16,
backgroundColor: Color(red: 1.0, green: 0.765, blue: 0.220), // PayPal gold
borderColor: .clear,
borderWidth: 0,
cornerRadius: 3,
padding: EdgeInsets(top: 16, leading: 12, bottom: 16, trailing: 12),
opacity: 1.0,
icon: IconStyle(size: CGSize(width: 60, height: 16))
)Override the default appearance with custom styles for three button states:
let config = PayoutSubmissionComponentConfig(
submitText: "Withdraw Now",
styles: ButtonStateStyles(
base: FieldStyle(
color: .white,
fontSize: 18,
backgroundColor: Color(red: 0.2, green: 0.7, blue: 0.3), // Green
borderColor: Color(red: 0.2, green: 0.7, blue: 0.3),
borderWidth: 0,
cornerRadius: 12,
padding: EdgeInsets(top: 16, leading: 32, bottom: 16, trailing: 32),
opacity: 1.0
),
disabled: FieldStyle(
color: Color(.systemGray2),
backgroundColor: Color(.systemGray5),
borderColor: Color(.systemGray5),
opacity: 0.5
),
loading: FieldStyle(
color: .white,
backgroundColor: Color(red: 0.15, green: 0.55, blue: 0.25), // Darker green
opacity: 0.9
)
)
)| Style State | Description |
|---|---|
base | The normal interactive state. Applied when the button is ready to be clicked. |
disabled | The disabled state. Can be set by calling setDisabled(_:) on the component. |
loading | The loading state. Applied during payout processing. |
Apply your brand colours:
struct BrandColors {
static let primary = Color(red: 0.17, green: 0.24, blue: 0.31)
static let accent = Color(red: 0.20, green: 0.60, blue: 0.86)
static let success = Color(red: 0.18, green: 0.80, blue: 0.44)
}
let config = PayoutSubmissionComponentConfig(
submitText: "Complete Withdrawal",
styles: ButtonStateStyles(
base: FieldStyle(
color: .white,
fontSize: 16,
backgroundColor: BrandColors.success,
borderColor: BrandColors.success,
borderWidth: 0,
cornerRadius: 8,
padding: EdgeInsets(top: 14, leading: 28, bottom: 14, trailing: 28)
),
disabled: FieldStyle(
backgroundColor: Color(.systemGray4),
opacity: 0.6
),
loading: FieldStyle(
backgroundColor: BrandColors.primary,
opacity: 0.9
)
)
)The submission button applies the following FieldStyle properties:
| Property | Type | Description | Example |
|---|---|---|---|
color | Color? | The text/foreground colour. | .white |
font | Font? | The SwiftUI font for the text. | .body |
fontSize | CGFloat? | The font size in points. | 16 |
backgroundColor | Color? | The background colour. | Color(red: 0.0, green: 0.44, blue: 0.73) |
borderColor | Color? | The border colour. | .blue |
borderWidth | CGFloat? | The border width in points. | 1 |
cornerRadius | CGFloat? | The corner radius in points. | 8 |
padding | EdgeInsets? | The padding around the content. | EdgeInsets(top: 14, leading: 24, bottom: 14, trailing: 24) |
opacity | Double? | The opacity (0.0 - 1.0). | 0.8 |
icon | IconStyle? | Icon styling with size and tint colour. | IconStyle(size: CGSize(width: 24, height: 24), tintColor: .white) |
Renders the submit button in SwiftUI:
struct PayoutView: View {
@State private var submissionComponent: BaseComponent?
var body: some View {
VStack {
if let component = submissionComponent {
component.buildContent()
.frame(maxWidth: .infinity)
.frame(height: 50)
}
}
.onAppear {
createSubmissionComponent()
}
}
private func createSubmissionComponent() {
Task {
let config = PayoutSubmissionComponentConfig(
submitText: "Withdraw to PayPal",
onPostPayout: { result in
print("Success: \(result.merchantTransactionId)")
}
)
let component = try pxpCheckout.create(.payoutSubmission, componentConfig: config)
await MainActor.run {
self.submissionComponent = component
}
}
}
}A simple implementation with essential callbacks:
import SwiftUI
import PXPCheckoutSDK
struct BasicSubmissionView: View {
@State private var submissionComponent: BaseComponent?
@State private var payoutStatus: String?
var body: some View {
VStack(spacing: 20) {
Text("Withdraw Funds")
.font(.title)
if let component = submissionComponent {
component.buildContent()
.frame(maxWidth: .infinity)
.frame(height: 50)
}
if let status = payoutStatus {
Text(status)
.foregroundColor(.secondary)
}
}
.padding()
.onAppear {
createComponent()
}
}
private func createComponent() {
Task {
let config = PayoutSubmissionComponentConfig(
submitText: "Withdraw to PayPal",
onClick: {
print("Button clicked")
},
onPostPayout: { result in
Task { @MainActor in
payoutStatus = "Payout completed! TX: \(result.merchantTransactionId)"
}
},
onError: { error in
Task { @MainActor in
if let sdkError = error as? BaseSdkException {
payoutStatus = "Error: \(sdkError.errorMessage)"
} else {
payoutStatus = "Error: \(error.localizedDescription)"
}
}
}
)
let component = try pxpCheckout.create(.payoutSubmission, componentConfig: config)
await MainActor.run {
self.submissionComponent = component
}
}
}
}Implementation with SwiftUI alert for approval:
struct ApprovalPayoutView: View {
@State private var submissionComponent: BaseComponent?
@State private var showApprovalAlert = false
@State private var pendingApprovalContinuation: CheckedContinuation<PrePayoutSubmitResult?, Never>?
let payoutAmount: Decimal = 100.00
var body: some View {
VStack(spacing: 20) {
Text("Withdraw $\(String(format: "%.2f", payoutAmount))")
.font(.title)
if let component = submissionComponent {
component.buildContent()
.frame(maxWidth: .infinity)
.frame(height: 50)
}
}
.padding()
.alert("Confirm Payout", isPresented: $showApprovalAlert) {
Button("Cancel", role: .cancel) {
pendingApprovalContinuation?.resume(returning: nil)
pendingApprovalContinuation = nil
}
Button("Confirm") {
pendingApprovalContinuation?.resume(returning: PrePayoutSubmitResult(isApproved: true))
pendingApprovalContinuation = nil
}
} message: {
Text("Send $\(String(format: "%.2f", payoutAmount)) to your PayPal account?")
}
.onAppear {
createComponent()
}
}
private func createComponent() {
Task {
let config = PayoutSubmissionComponentConfig(
submitText: "Withdraw Now",
onPrePayoutSubmit: { [self] in
return await withCheckedContinuation { continuation in
Task { @MainActor in
self.pendingApprovalContinuation = continuation
self.showApprovalAlert = true
}
}
},
onPostPayout: { result in
print("Payout completed: \(result.merchantTransactionId)")
showSuccessScreen()
},
onError: { error in
if let sdkError = error as? BaseSdkException {
showErrorDialog(sdkError.errorMessage)
} else {
showErrorDialog(error.localizedDescription)
}
}
)
let component = try pxpCheckout.create(.payoutSubmission, componentConfig: config)
await MainActor.run {
self.submissionComponent = component
}
}
}
}Full implementation with all three direct payout components:
import SwiftUI
import PXPCheckoutSDK
struct DirectPayoutView: View {
@State private var pxpCheckout: PxpCheckout?
@State private var amountComponent: BaseComponent?
@State private var receiverComponent: BaseComponent?
@State private var submissionComponent: BaseComponent?
@State private var isLoading = true
@State private var showApprovalAlert = false
@State private var showSuccessAlert = false
@State private var pendingApprovalContinuation: CheckedContinuation<PrePayoutSubmitResult?, Never>?
@State private var completedTransactionId: String?
let payoutAmount: Decimal = 100.00
let storedPayPalEmail: String = "user@example.com"
let storedPayerId: String = "PAYERID123ABC"
var body: some View {
NavigationView {
VStack(spacing: 24) {
if isLoading {
ProgressView("Loading...")
} else {
// Header
VStack(spacing: 8) {
Text("Withdraw to PayPal")
.font(.title)
.fontWeight(.bold)
Text("Funds will arrive in 1-2 business days")
.font(.subheadline)
.foregroundColor(.secondary)
}
// Amount display
if let amount = amountComponent {
amount.buildContent()
.frame(maxWidth: .infinity)
}
// Receiver display
if let receiver = receiverComponent {
receiver.buildContent()
.frame(maxWidth: .infinity)
}
Spacer()
// Submit button
if let submit = submissionComponent {
submit.buildContent()
.frame(maxWidth: .infinity)
.frame(height: 50)
}
}
}
.padding()
.alert("Confirm Payout", isPresented: $showApprovalAlert) {
Button("Cancel", role: .cancel) {
pendingApprovalContinuation?.resume(returning: nil)
pendingApprovalContinuation = nil
}
Button("Confirm") {
pendingApprovalContinuation?.resume(returning: PrePayoutSubmitResult(isApproved: true))
pendingApprovalContinuation = nil
}
} message: {
Text("Send $\(String(format: "%.2f", payoutAmount)) to \(storedPayPalEmail)?")
}
.alert("Payout Successful!", isPresented: $showSuccessAlert) {
Button("Done") {
// Navigate away
}
} message: {
Text("Your payout has been sent. Transaction ID: \(completedTransactionId ?? "")")
}
.onAppear {
initializePayoutComponents()
}
}
}
private func initializePayoutComponents() {
Task {
do {
// Initialise SDK with stored credentials
let sessionData = SessionData(
sessionId: "your-session-id",
hmacKey: "your-hmac-key",
encryptionKey: "your-encryption-key"
)
let transactionData = TransactionData(
amount: payoutAmount,
currency: "USD",
entryType: .ecom,
intent: TransactionIntentData(card: nil, paypal: .payout),
merchantTransactionId: "payout-\(UUID().uuidString)",
merchantTransactionDate: { Date() }
)
let paypalConfig = PayPalConfig(
payout: PayPalPayoutConfig(
paypalWallet: PayPalWallet(
email: storedPayPalEmail,
payerId: storedPayerId,
proceedPayoutWithSdk: true
)
)
)
let checkoutConfig = CheckoutConfig(
environment: .test,
session: sessionData,
transactionData: transactionData,
merchantShopperId: "user-123",
ownerId: "merchant-456",
paypalConfig: paypalConfig
)
let pxpCheckout = try PxpCheckout.initialize(config: checkoutConfig)
self.pxpCheckout = pxpCheckout
// Create amount component
let amountConfig = PayoutAmountComponentConfig(
label: "Payout Amount",
styles: FieldStyle(
backgroundColor: Color(.systemGray6),
cornerRadius: 12,
padding: EdgeInsets(top: 20, leading: 20, bottom: 20, trailing: 20),
textAlignment: .center
),
inputStyles: FieldInputStateStyles(
base: FieldInputStyle(
color: Color(red: 0.2, green: 0.7, blue: 0.3),
fontSize: 32,
fontWeight: .bold
)
)
)
let amount = try pxpCheckout.create(.payoutAmount, componentConfig: amountConfig)
// Create receiver component
let receiverConfig = PayPalPayoutReceiverComponentConfig(
label: "PayPal Account",
applyMask: true,
showMaskToggle: true,
styles: FieldStyle(
backgroundColor: Color(red: 0.0, green: 0.44, blue: 0.73).opacity(0.05),
borderColor: Color(red: 0.0, green: 0.44, blue: 0.73).opacity(0.3),
borderWidth: 1,
cornerRadius: 12,
padding: EdgeInsets(top: 16, leading: 20, bottom: 16, trailing: 20)
)
)
let receiver = try pxpCheckout.create(.paypalPayoutReceiver, componentConfig: receiverConfig)
// Create submission component
let submissionConfig = PayoutSubmissionComponentConfig(
submitText: "Withdraw to PayPal",
submitAccessibilityLabel: "Withdraw funds to PayPal account",
styles: ButtonStateStyles(
base: FieldStyle(
color: .white,
fontSize: 18,
backgroundColor: Color(red: 0.0, green: 0.44, blue: 0.73),
cornerRadius: 12,
padding: EdgeInsets(top: 16, leading: 32, bottom: 16, trailing: 32)
),
disabled: FieldStyle(
backgroundColor: Color(.systemGray4),
opacity: 0.6
),
loading: FieldStyle(
backgroundColor: Color(red: 0.0, green: 0.37, blue: 0.65),
opacity: 0.9
)
),
onClick: {
print("Withdraw button clicked")
Analytics.track("direct_payout_clicked")
},
onPrePayoutSubmit: { [self] in
return await withCheckedContinuation { continuation in
Task { @MainActor in
self.pendingApprovalContinuation = continuation
self.showApprovalAlert = true
}
}
},
onPostPayout: { [self] result in
print("Payout completed: \(result.merchantTransactionId)")
Task { @MainActor in
completedTransactionId = result.merchantTransactionId
showSuccessAlert = true
}
Analytics.track("direct_payout_completed", properties: [
"transactionId": result.merchantTransactionId,
"amount": payoutAmount
])
},
onError: { error in
guard let sdkError = error as? BaseSdkException else {
print("Payout error: \(error.localizedDescription)")
Task { @MainActor in
showErrorAlert(error.localizedDescription)
}
Analytics.track("direct_payout_error", properties: [
"errorCode": "UNKNOWN"
])
return
}
print("Payout error: \(sdkError.errorCode) - \(sdkError.errorMessage)")
Task { @MainActor in
showErrorAlert(sdkError.errorMessage)
}
Analytics.track("direct_payout_error", properties: [
"errorCode": sdkError.errorCode
])
}
)
let submission = try pxpCheckout.create(.payoutSubmission, componentConfig: submissionConfig)
await MainActor.run {
self.amountComponent = amount
self.receiverComponent = receiver
self.submissionComponent = submission
self.isLoading = false
}
} catch {
print("Initialisation error: \(error.localizedDescription)")
await MainActor.run {
self.isLoading = false
}
}
}
}
}The submission component can throw errors during component creation and during payout execution:
These errors are thrown when calling pxpCheckout.create(.payoutSubmission, ...) and must be caught with try-catch:
| Error code | Exception | Description |
|---|---|---|
| SDK0803 | PaypalPayoutReceiverRequiredException | The receiver is required. |
| SDK0804 | PayoutReceiverTypeInvalidException | Invalid receiver type. |
| SDK0806 | PayoutWalletInvalidException | Invalid wallet value. Only .paypal is currently supported. |
| SDK0807 | PayoutWalletRequiresEmailException | The PayPal wallet configuration requires an email address. Both email and payerId must be provided in paypalConfig.payout.paypalWallet. |
| SDK0808 | PaypalPayoutEmailInvalidException | Invalid email format. |
| SDK0809 | PayoutPayerIdRequiredException | The payer ID is required. |
| SDK0816 | PayoutEmailMaxLengthException | The PayPal email address exceeds the maximum length. |
| SDK0817 | PayoutPayerIdMaxLengthException | The payer ID exceeds 13 characters. |
| SDK0818 | PayoutPayerIdInvalidException | The payer ID contains invalid characters. |
These errors are passed to the onError callback during payout execution:
| Error code | Exception | Description |
|---|---|---|
| SDK0816 | PayoutEmailMaxLengthException | The PayPal email address exceeds the maximum length. |
| SDK0817 | PayoutPayerIdMaxLengthException | The payer ID exceeds 13 characters. |
| SDK0818 | PayoutPayerIdInvalidException | The payer ID contains invalid characters. |
| SDK0819 | PayoutFailedException | The payout transaction failed. May also include backend/API/HTTP error codes. |
Amount and currency validation errors (SDK0810-SDK0815) belong to the PayoutAmountComponent and won't appear in submission component errors.
For a complete list of error codes, see Data validation.
do {
let config = PayoutSubmissionComponentConfig(
submitText: "Withdraw to PayPal",
onError: { error in
// Handle execution-time errors
handlePayoutError(error)
}
)
let component = try pxpCheckout.create(.payoutSubmission, componentConfig: config)
} catch let error as BaseSdkException {
// Handle creation-time errors
let userMessage: String
switch error.errorCode {
case "SDK0803", "SDK0804", "SDK0808":
userMessage = "Invalid PayPal account configuration. Please contact support."
case "SDK0806":
userMessage = "Invalid wallet type. Only PayPal wallets are currently supported."
case "SDK0807":
userMessage = "PayPal wallet configuration is incomplete. Both email and payer ID are required."
case "SDK0809", "SDK0816", "SDK0817", "SDK0818":
userMessage = "PayPal account information is invalid. Please re-link your account."
default:
userMessage = "Failed to initialize payout. Please contact support."
}
showErrorDialog(title: "Configuration Error", message: userMessage)
}config.onError = { error in
guard let sdkError = error as? BaseSdkException else {
Task { @MainActor in
showErrorDialog(
title: "Payout Error",
message: "An error occurred. Please try again.",
showRetryButton: true
)
}
return
}
let userMessage: String
let shouldRetry: Bool
switch sdkError.errorCode {
case "SDK0816", "SDK0817", "SDK0818":
userMessage = "PayPal account information is invalid. Please re-link your account."
shouldRetry = false
case "SDK0819":
userMessage = "Payout transaction failed. Please try again."
shouldRetry = true
default:
// May include backend/API/HTTP error codes
userMessage = "An error occurred. Please try again."
shouldRetry = true
}
Task { @MainActor in
showErrorDialog(
title: "Payout Error",
message: userMessage,
showRetryButton: shouldRetry
)
}
}Before using the payout submission component, ensure you have:
- Configured the transaction data with a valid payout amount:
let payoutAmount: Decimal = 100.00
let transactionData = TransactionData(
amount: payoutAmount,
currency: "USD",
entryType: .ecom,
intent: TransactionIntentData(card: nil, paypal: .payout),
merchantTransactionId: "payout-\(UUID().uuidString)",
merchantTransactionDate: { Date() }
)- Initialised the SDK with stored PayPal credentials:
let sessionData = SessionData(
sessionId: "your-session-id",
hmacKey: "your-hmac-key",
encryptionKey: "your-encryption-key"
)
let paypalConfig = PayPalConfig(
payout: PayPalPayoutConfig(
paypalWallet: PayPalWallet(
email: "user@example.com", // Required: Stored from previous OAuth
payerId: "PAYERID123ABC", // Required: Stored from previous OAuth
proceedPayoutWithSdk: true // Required for payout execution
)
)
)
let checkoutConfig = CheckoutConfig(
environment: .test,
session: sessionData,
transactionData: transactionData,
merchantShopperId: "user-123",
ownerId: "merchant-456",
paypalConfig: paypalConfig
)
let pxpCheckout = try PxpCheckout.initialize(config: checkoutConfig)Both paypalConfig.payout.paypalWallet.email and paypalConfig.payout.paypalWallet.payerId are required in CheckoutConfig before create(.payoutSubmission, ...) can succeed. These credentials should be stored from a previous PayPal OAuth flow.
For the SDK to execute payouts, all three conditions must be met:
proceedPayoutWithSdkmust be set totrue.onPrePayoutSubmitcallback must be provided in the component configuration.onPrePayoutSubmitmust returnPrePayoutSubmitResult(isApproved: true).
If any condition isn't met, the payout won't execute.