Learn how to fix common issues with the PayPal component on iOS.
| Issue | Description | Next steps |
|---|---|---|
| PayPal SDK not loading | The Console shows PayPal SDK loading errors. This could be due to network issues, incorrect configuration, or SSL/TLS errors. | |
|
| Button not rendering | The PayPal button doesn't appear in the SwiftUI view. |
|
| Validation errors | You receive errors via the onSubmitError callback. |
|
| Funding source issues | Certain funding sources not available. |
|
| Network connectivity | The SDK fails to load and there are timeout errors. |
|
| SSL/TLS certificate errors | Payment flow fails to load with SSL errors. This could be due to expired certificates or incorrect device time. |
|
| Memory issues | The app crashes or becomes unresponsive during payment. |
|
| SwiftUI state issues | Component state is lost or doesn't update. |
|
| Event callbacks not firing | onApprove, onError, or other callbacks aren't called. |
|
| Payment authorisation fails | The payment starts but fails to complete. For example, due to insufficient PayPal funds, declined payment method, or fraud detection. |
|
| Button styling not applied | The button doesn't match the expected style. |
|
| Locale/language issues | The button is displayed in the wrong language. |
|
| Consent component not working | The consent checkbox doesn't affect payment. |
|
| Custom content not displaying | Custom SwiftUI content for button not showing. |
|
| Simulator vs device differences | Payment works in simulator but fails on device (or vice versa). |
|
| App Store rejection | App rejected due to PayPal integration issues. |
|
To diagnose issues, enable comprehensive logging.
Use Console.app or Xcode's console to view logs:
let config = PayPalButtonComponentConfig()
// Add verbose logging to callbacks
config.onClick = {
print("PayPal button clicked")
}
config.onApprove = { approvalData in
print("✓ Payment successful")
print("Order ID: \(approvalData.orderID)")
print("Payer ID: \(approvalData.payerID)")
}
config.onError = { error in
print("✗ Payment error: \(error.errorMessage)")
print("Error code: \(error.errorCode)")
}
config.onCancel = { error in
print("⚠ Payment cancelled")
print("Cancellation reason: \(error.errorMessage)")
}
config.onOrderCreated = { submitResult in
print("📦 Order created")
print("Merchant TX ID: \(submitResult.merchantTransactionId)")
print("System TX ID: \(submitResult.systemTransactionId)")
}Use Console.app filters to view specific logs:
# PXP Checkout logs
subsystem:com.pxp.checkout
# PayPal-specific logs
process:YourAppName AND paypal
# All SDK logs
category:PXPCheckoutSDKEnable detailed logging in Xcode:
- Open your scheme by going to Product > Scheme > Edit Scheme.
- Select Run > Arguments.
- Add these environment variables:
OS_ACTIVITY_MODE=disable(for cleaner logs)IDEPreferLogStreaming=YES
Use this network monitoring implementation before initiating payment flows or when diagnosing network-related errors. The PayPal SDK requires an active internet connection, and this snippet uses Apple's Network framework to provide real-time network status updates. Call this before creating PayPal components or implement continuous monitoring throughout your app's lifecycle.
import Network
class NetworkMonitor {
static let shared = NetworkMonitor()
private let monitor = NWPathMonitor()
private let queue = DispatchQueue(label: "NetworkMonitor")
var isConnected: Bool = false
func startMonitoring() {
monitor.pathUpdateHandler = { path in
self.isConnected = path.status == .satisfied
if !self.isConnected {
print("⚠ Network disconnected")
} else {
print("✓ Network connected")
}
}
monitor.start(queue: queue)
}
func stopMonitoring() {
monitor.cancel()
}
}
// Usage example:
NetworkMonitor.shared.startMonitoring()
if !NetworkMonitor.shared.isConnected {
showAlert(title: "No Connection",
message: "Please check your internet connection and try again.")
return
}
// Proceed with PayPal SDK initialisationMonitor memory usage during performance testing or when diagnosing crashes to identify leaks or excessive usage. This function reports current memory usage and helps detect potential memory issues. For comprehensive memory profiling, use Xcode Instruments alongside this logging approach.
func logMemoryUsage() {
var taskInfo = mach_task_basic_info()
var count = mach_msg_type_number_t(MemoryLayout<mach_task_basic_info>.size) / 4
let result = withUnsafeMutablePointer(to: &taskInfo) {
$0.withMemoryRebound(to: integer_t.self, capacity: 1) {
task_info(mach_task_self_, task_flavor_t(MACH_TASK_BASIC_INFO), $0, &count)
}
}
if result == KERN_SUCCESS {
let usedMemory = Double(taskInfo.resident_size) / 1024 / 1024
print("Memory usage: \(String(format: "%.2f", usedMemory)) MB")
if usedMemory > 200 {
print("⚠ High memory usage detected")
}
}
}
// Usage example:
logMemoryUsage()
// Create PayPal component
logMemoryUsage() // Check memory after creationValidate your PayPal configuration before creating components to catch errors early. This validation function checks required fields and ensures configuration consistency. Call it after building your configuration object but before attempting to create the component.
struct ValidationResult {
let isValid: Bool
let errors: [String]
}
func validatePayPalConfig(_ config: PayPalButtonComponentConfig) -> ValidationResult {
var errors: [String] = []
if config.payeeEmailAddress != nil && !isValidEmail(config.payeeEmailAddress!) {
errors.append("payeeEmailAddress format is invalid")
}
if config.fundingSource == nil {
errors.append("fundingSource is required")
}
return ValidationResult(
isValid: errors.isEmpty,
errors: errors
)
}
func isValidEmail(_ email: String) -> Bool {
let emailRegex = "[A-Z0-9a-z._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,64}"
let emailPredicate = NSPredicate(format: "SELF MATCHES %@", emailRegex)
return emailPredicate.evaluate(with: email)
}
// Usage example:
let config = PayPalButtonComponentConfig()
config.fundingSource = .paypal
config.payeeEmailAddress = "merchant@example.com"
let validation = validatePayPalConfig(config)
if !validation.isValid {
print("❌ Configuration errors: \(validation.errors.joined(separator: ", "))")
return
}
// Proceed with component creationVerify device capabilities before initialising PayPal to ensure the device meets minimum requirements. This function checks iOS version, network connectivity, and available memory to confirm the device can support PayPal payments. Call it during app launch or before initiating payment flows.
func checkDeviceCapabilities() -> (canProceed: Bool, issues: [String]) {
var issues: [String] = []
// Check iOS version
if #unavailable(iOS 13.0) {
issues.append("iOS 13.0 or later required")
}
// Check network capability
if !NetworkMonitor.shared.isConnected {
issues.append("Network connection required")
}
// Check memory (simplified check)
let physicalMemory = ProcessInfo.processInfo.physicalMemory
let memoryGB = Double(physicalMemory) / 1024 / 1024 / 1024
if memoryGB < 1.0 {
issues.append("Low device memory may cause issues")
}
return (issues.isEmpty, issues)
}
// Usage example:
let (canProceed, issues) = checkDeviceCapabilities()
if !canProceed {
print("⚠ Device capability issues: \(issues.joined(separator: ", "))")
showAlert(title: "Device requirements",
message: "This device may not support PayPal payments: \(issues.joined(separator: ", "))")
}Use these debugging extensions when components don't appear or layout issues occur. The extensions help identify view hierarchy and layout problems by adding visual borders and console logging. Add them to parent views during debugging to understand rendering and layout behaviour.
extension View {
func debugPrint(_ message: String) -> some View {
print("🔍 \(message)")
return self
}
func debugBorder(_ color: Color = .red) -> some View {
self.border(color, width: 2)
}
}
// Usage example:
struct PaymentView: View {
var body: some View {
VStack {
if let component = paypalComponent {
component.buildContent()
.frame(height: 50)
.debugBorder(.red) // Visualise button area
.debugPrint("PayPal button rendered")
} else {
Text("Loading...")
.debugPrint("Still loading component")
}
}
.debugBorder(.blue) // Visualise container
}
}Implement proper error handling when SDK or component initialisation might fail. This pattern captures detailed error information and provides appropriate user feedback while logging to analytics. Use this approach in async component creation flows to ensure graceful failure handling.
@MainActor
func createPayPalComponent() async {
do {
let pxpCheckout = try PxpCheckout.initialize(config: checkoutConfig)
let config = PayPalButtonComponentConfig()
config.fundingSource = .paypal
let component = try pxpCheckout.create(
.paypalButton,
componentConfig: config
)
self.paypalComponent = component
self.isLoading = false
} catch let error as NSError {
print("❌ Initialisation error: \(error.localizedDescription)")
print("Error domain: \(error.domain)")
print("Error code: \(error.code)")
print("Error userInfo: \(error.userInfo)")
self.errorMessage = "Failed to initialise PayPal: \(error.localizedDescription)"
self.isLoading = false
// Track error for analytics
logError(error)
}
}
func logError(_ error: Error) {
// Log to your analytics/crash reporting service
print("📊 Logging error to analytics")
}Always test thoroughly in sandbox before production:
let config = PayPalButtonComponentConfig()
config.fundingSource = .paypal
// Configure test callbacks
config.onApprove = { approvalData in
print("✓ SANDBOX: Payment approved: \(approvalData.orderID)")
// Test your success flow
}
config.onError = { error in
print("❌ SANDBOX: Error: \(error.errorMessage)")
// Test your error handling
}Test on various iOS devices and versions:
- iPhone (various models).
- iPad (if supported).
- Different iOS versions.
- Different network conditions (WiFi, cellular, airplane mode).
- Different regions/locales.
Deliberately test error conditions:
- Network interruptions. Use the Network Link Conditioner.
- Insufficient funds. Use sandbox test accounts.
- Cancelled payments.
- Timeout scenarios.
- Invalid session configurations.