Capture funds instantly upon customer approval for iOS.
The pay now flow is designed for immediate payment capture, making it ideal for digital products, services, or when instant payment confirmation is required.
The PayPal pay now flow consists of five key steps for immediate payment processing.
The customer taps the PayPal button, which triggers the payment flow. The SDK validates the PayPal configuration and transaction data before proceeding to order creation. If validation fails, onError is triggered.
The SDK creates a PayPal order using the provided transaction details. This involves sending the payment amount, currency, merchant information, and any additional order details to PayPal's API. If order creation fails, onError is triggered.
The customer is redirected to PayPal where they can log in and approve the payment.
This step has three associated callbacks:
onApprove: Proceeds with payment capture if the customer successfully approves the payment.onCancel: Cancels the transaction if the customer cancels the payment.onError: Receives error data if any error occurs during the approval process.
PayPal handles all authentication and payment method selection within their secure environment.
Once the customer approves the payment, the SDK processes the payment immediately and automatically captures the funds. This happens within the onApprove callback handler, where you process the capture and handle success/failure.
You receive the final payment result from PayPal and the onApprove callback completes processing. The transaction is either completed successfully with payment confirmation, or fails with specific error details.
To use PayPal pay now payments in your iOS application:
- Ensure you have a valid PayPal Business account with API credentials.
- Configure your PayPal merchant account to accept the currencies you need.
- Set up your merchant configuration in the Unity Portal (API credentials, payment methods, risk settings).
- Install and configure the PXP Checkout SDK for iOS.
Set up your SDK configuration with transaction information for PayPal payments. For pay now, use .purchase intent, which maps to PayPal's capture intent.
Intent for Pay Now: For immediate payment capture (pay now flow), use .purchase intent, which maps to PayPal's "capture" intent.
import PXPCheckoutSDK
// Create session data (obtained from your backend)
let sessionData = SessionData(
sessionId: "your-session-id",
hmacKey: "your-hmac-key",
encryptionKey: "your-encryption-key"
)
// Create transaction data for pay now flow
let transactionData = TransactionData(
amount: 25.00, // Payment amount
currency: "USD",
entryType: .ecom,
intent: TransactionIntentData(card: nil, paypal: .purchase), // For immediate capture (pay now)
merchantTransactionId: UUID().uuidString,
merchantTransactionDate: { Date() }
)
// Create checkout configuration
let checkoutConfig = CheckoutConfig(
environment: .test, // or .live
session: sessionData,
transactionData: transactionData,
merchantShopperId: "shopper-123",
ownerId: "owner-456"
)
// Initialise PxpCheckout SDK
let pxpCheckout = try PxpCheckout.initialize(config: checkoutConfig)Implement the required callbacks for PayPal pay now payments.
// Create PayPal component configuration
let config = PayPalButtonComponentConfig()
config.fundingSource = .paypal
config.shippingPreference = .noShipping
config.userAction = .payNow
config.payeeEmailAddress = "merchant@example.com"
config.paymentDescription = "Product purchase"
// REQUIRED: Handle successful payment approval
config.onApprove = { approvalData in
print("Payment approved: Order ID: \(approvalData.orderID), Payer ID: \(approvalData.payerID)")
// Process the payment on your backend
Task {
do {
let result = try await yourBackendService.capturePayPalPayment(
orderID: approvalData.orderID,
payerID: approvalData.payerID,
merchantTransactionId: transactionId
)
if result.status == "COMPLETED" {
print("Payment captured successfully: \(result.id)")
// Navigate to success screen
await MainActor.run {
navigationCoordinator.navigateToSuccess(orderID: approvalData.orderID)
}
} else {
throw PaymentError.captureFailed(result.error ?? "Unknown error")
}
} catch {
print("Payment processing error: \(error.localizedDescription)")
await MainActor.run {
showError("Payment failed. Please try again.")
}
}
}
}
// REQUIRED: Handle payment errors
config.onError = { error in
print("Payment error: \(error.errorMessage)")
showError("Payment failed. Please try again.")
}
// OPTIONAL: Handle payment cancellation
config.onCancel = { error in
print("Payment cancelled by user")
showMessage("Payment was cancelled. You can try again anytime.")
}Create and render the PayPal component in your SwiftUI view.
import SwiftUI
import PXPCheckoutSDK
struct PayPalPaymentView: View {
@StateObject private var viewModel: PaymentViewModel
@State private var paypalComponent: BaseComponent?
var body: some View {
VStack(spacing: 16) {
Text("Complete your payment")
.font(.title)
Text("Amount: $25.00")
.font(.title2)
// PayPal button
if let component = paypalComponent {
component.buildContent()
.frame(height: 50)
} else {
ProgressView()
.onAppear {
createPayPalComponent()
}
}
}
.padding()
}
private func createPayPalComponent() {
do {
let config = PayPalButtonComponentConfig()
config.fundingSource = .paypal
// Configure callbacks
config.onApprove = { approvalData in
viewModel.handlePaymentApproval(approvalData)
}
config.onError = { error in
viewModel.handlePaymentError(error)
}
config.onCancel = { error in
viewModel.handlePaymentCancellation()
}
// Create component
let component = try pxpCheckout.create(
.paypalButton,
componentConfig: config
)
self.paypalComponent = component
} catch {
print("Failed to create PayPal component: \(error)")
}
}
}Use different processing logic based on transaction amounts.
config.onApprove = { approvalData in
let amount = transactionData.amount
// Add additional verification for high-value transactions
if amount > 100.00 { // Over $100.00
Task {
let confirmed = await showConfirmationDialog(
message: "Confirm payment of $\(amount)?"
)
if confirmed {
await processPayPalPayment(approvalData)
} else {
await MainActor.run {
showMessage("Payment cancelled by user.")
}
}
}
} else {
Task {
await processPayPalPayment(approvalData)
}
}
}Handle different customer types with varying processing requirements.
enum CustomerType {
case new, returning, vip
}
struct ProcessingOptions {
let verificationLevel: VerificationLevel
let emailConfirmation: Bool
let fastProcessing: Bool
}
func getCustomerProcessingOptions(customerType: CustomerType) -> ProcessingOptions {
switch customerType {
case .new:
return ProcessingOptions(
verificationLevel: .enhanced,
emailConfirmation: true,
fastProcessing: false
)
case .returning:
return ProcessingOptions(
verificationLevel: .standard,
emailConfirmation: false,
fastProcessing: false
)
case .vip:
return ProcessingOptions(
verificationLevel: .minimal,
emailConfirmation: false,
fastProcessing: true
)
}
}
config.onApprove = { approvalData in
let processingOptions = getCustomerProcessingOptions(customerType: .returning)
Task {
await processPayPalPayment(
approvalData: approvalData,
options: processingOptions,
customerType: .returning
)
}
}Implement comprehensive error handling for PayPal payments.
config.onError = { error in
print("Error: \(error.errorMessage)")
// Check error code
let errorCode = error.errorCode
let errorMessage = error.errorMessage
if errorCode == "PAYPAL_ERROR" {
// PayPal-specific errors - check message for details
if errorMessage.contains("declined") || errorMessage.contains("INSTRUMENT_DECLINED") {
showErrorDialog(
title: "Payment Declined",
message: "Your PayPal payment method was declined. Please try a different method."
)
} else if errorMessage.contains("verification") || errorMessage.contains("PAYER_ACTION_REQUIRED") {
showErrorDialog(
title: "Action Required",
message: "Additional action required in PayPal. Please complete the payment process."
)
} else {
showErrorDialog(
title: "Payment Error",
message: "Payment failed: \(errorMessage)"
)
}
} else if errorCode == "VALIDATION_FAILED" {
showErrorDialog(
title: "Validation Error",
message: "Payment details validation failed. Please try again."
)
} else {
showErrorDialog(
title: "Payment Error",
message: "Payment failed. Please try again or contact support."
)
}
}
config.onApprove = { approvalData in
Task {
do {
try await processPayPalPayment(approvalData)
} catch let error as HTTPError {
// Handle backend processing errors
switch error.statusCode {
case 422:
await MainActor.run {
showError("Payment details could not be verified. Please try again.")
}
case 409:
await MainActor.run {
showError("This payment has already been processed.")
}
case 500...599:
await MainActor.run {
showError("Payment system temporarily unavailable. Please try again later.")
}
default:
await MainActor.run {
showError("Payment processing failed. Please try again.")
}
}
}
}
}The following example shows a complete PayPal pay now implementation with SwiftUI.
import SwiftUI
import PXPCheckoutSDK
struct PayPalPayNowView: View {
@StateObject private var viewModel: PaymentViewModel
@State private var paypalComponent: BaseComponent?
@State private var showLoadingSpinner = false
@State private var showSuccessDialog = false
@State private var showErrorDialog = false
@State private var paymentResult: PaymentResult?
@State private var errorMessage: String?
var body: some View {
VStack(spacing: 16) {
Text("Complete your payment")
.font(.title)
Text("Amount: $25.00")
.font(.title2)
// PayPal button
if let component = paypalComponent {
component.buildContent()
.frame(height: 50)
} else {
ProgressView()
}
// Loading indicator
if showLoadingSpinner {
ProgressView()
.scaleEffect(1.5)
}
}
.padding()
.onAppear {
createPayPalComponent()
}
.alert("Payment Successful", isPresented: $showSuccessDialog) {
Button("OK") {
showSuccessDialog = false
// Navigate or dismiss
}
} message: {
Text("Your payment has been processed successfully!")
}
.alert("Payment Error", isPresented: $showErrorDialog) {
Button("OK") {
showErrorDialog = false
}
} message: {
Text(errorMessage ?? "An error occurred")
}
}
private func createPayPalComponent() {
do {
let config = PayPalButtonComponentConfig()
// PayPal configuration
config.fundingSource = .paypal
config.payeeEmailAddress = "merchant@example.com"
config.paymentDescription = "Product Purchase - $25.00"
config.shippingPreference = .noShipping
config.userAction = .payNow
// Styling
config.style = PayPalButtonStyleConfig(
color: .gold,
label: .paypal,
size: .expanded,
edges: .rounded
)
// Step 1: Handle payment approval
config.onApprove = { approvalData in
print("Processing PayPal payment")
self.showLoadingSpinner = true
Task {
do {
print("Order ID: \(approvalData.orderID)")
print("Payer ID: \(approvalData.payerID)")
// Process payment on backend
let result = try await viewModel.capturePayment(
orderID: approvalData.orderID,
payerID: approvalData.payerID
)
await MainActor.run {
self.showLoadingSpinner = false
if result.isSuccess {
print("Payment completed successfully")
self.paymentResult = result.data
self.showSuccessDialog = true
} else {
self.errorMessage = result.error ?? "Payment capture failed"
self.showErrorDialog = true
}
}
} catch {
print("Payment processing failed: \(error)")
await MainActor.run {
self.showLoadingSpinner = false
self.errorMessage = error.localizedDescription
self.showErrorDialog = true
}
}
}
}
// Step 2: Handle cancellation
config.onCancel = { error in
print("Payment cancelled by user")
// Show user-friendly message
}
// Step 3: Handle errors
config.onError = { error in
print("Payment error: \(error.errorMessage)")
self.showLoadingSpinner = false
self.errorMessage = error.errorMessage
self.showErrorDialog = true
}
// Create component
let component = try pxpCheckout.create(
.paypalButton,
componentConfig: config
)
self.paypalComponent = component
} catch {
print("Failed to create PayPal component: \(error)")
}
}
}This section describes the data received by the different callbacks as part of the PayPal pay now flow. This data is the same as for the confirm payment flow.
The onApprove callback receives payment approval data when the customer successfully approves the payment in PayPal. The approval data includes the PayPal order ID and payer information needed to capture the payment.
{
"orderID": "7YH53119ML8957234",
"payerID": "ABCDEFGHIJKLM"
}| Parameter | Description |
|---|---|
orderIDString required | The unique PayPal order ID that identifies this payment. |
payerIDString required | The unique PayPal payer ID that identifies the customer who approved the payment. |
Here's an example of what to do with this data:
config.onApprove = { approvalData in
print("Payment approved: \(approvalData)")
Task {
do {
// Capture the payment immediately for pay now flow
let captureResponse = try await repository.capturePayPalPayment(
orderID: approvalData.orderID,
payerID: approvalData.payerID,
merchantTransactionId: UUID().uuidString,
timestamp: Date()
)
if captureResponse.status == "COMPLETED" {
// Payment captured successfully
print("Payment captured: \(captureResponse.id)")
// Store transaction record
try await database.insertTransaction(
Transaction(
paypalOrderId: approvalData.orderID,
paypalPayerId: approvalData.payerID,
transactionId: captureResponse.id,
amount: captureResponse.amount,
currency: captureResponse.currency,
status: "completed",
timestamp: Date()
)
)
// Navigate to success
await MainActor.run {
navigationCoordinator.navigateToSuccess(orderID: approvalData.orderID)
}
} else {
throw PaymentError.captureFailed("Payment capture failed: \(captureResponse.status)")
}
} catch {
print("Payment capture error: \(error)")
await MainActor.run {
showError("Payment processing failed. Please contact support.")
}
}
}
}The onError callback receives error information when PayPal payments fail. PayPal errors include specific error names and details to help with troubleshooting.
{
"name": "VALIDATION_ERROR",
"message": "Invalid payment method",
"details": [{
"issue": "INSTRUMENT_DECLINED",
"description": "The instrument presented was either declined by the processor or bank, or it can't be used for this payment."
}]
}| Parameter | Description |
|---|---|
nameString | The error name. Possible values:
|
messageString | A human-readable error message. |
Here's an example of how to handle PayPal errors:
config.onError = { error in
print("Payment error: \(error.errorMessage)")
// Check error code
let errorCode = error.errorCode
let errorMessage = error.errorMessage
if errorCode == "PAYPAL_ERROR" {
// PayPal-specific errors - check message for details
if errorMessage.contains("declined") || errorMessage.contains("INSTRUMENT_DECLINED") {
showErrorDialog(
title: "Payment Declined",
message: "Your PayPal payment method was declined. Please try a different payment method."
)
} else if errorMessage.contains("verification") || errorMessage.contains("PAYER_ACTION_REQUIRED") {
showErrorDialog(
title: "Action Required",
message: "Additional verification required. Please complete the process in PayPal."
)
} else {
showErrorDialog(
title: "Payment Error",
message: "Payment failed: \(errorMessage)"
)
}
} else if errorCode == "VALIDATION_FAILED" {
showErrorDialog(
title: "Validation Error",
message: "Payment information is invalid. Please try again."
)
} else {
showErrorDialog(
title: "Payment Error",
message: "Payment failed. Please try again or contact support."
)
}
// Log error details for monitoring
Analytics.logEvent("paypal_payment_error", parameters: [
"error_code": errorCode,
"error_message": errorMessage,
"timestamp": Date().timeIntervalSince1970,
"payment_method": "paypal"
])
}The onCancel callback receives error information when the customer cancels the PayPal payment process.
Here's an example of how to handle cancellations:
config.onCancel = { error in
print("Payment cancelled by user")
// Log cancellation for analytics
Analytics.logEvent("paypal_payment_cancelled", parameters: [
"timestamp": Date().timeIntervalSince1970,
"payment_method": "paypal"
])
// Show user-friendly message
showMessage("Payment was cancelled. Your cart items are still saved.")
// Optional: Offer alternative payment methods
showAlternativePaymentOptions()
}