Adding Apple Pay Support in a SwiftUI App

Integrating Apple Pay into your SwiftUI app provides a seamless and secure payment option for your users. This guide will walk you through the steps of adding Apple Pay support to your application, from setting up your environment to handling transactions.

Why Integrate Apple Pay?

  • Convenience: Users can make purchases with a single touch or glance.
  • Security: Apple Pay uses tokenization to protect users’ financial information.
  • Increased Conversion: Simplified checkout process can lead to higher conversion rates.

Prerequisites

Before you begin, make sure you have the following:

  • An active Apple Developer account.
  • A configured Merchant ID in your Apple Developer account.
  • An iOS device capable of Apple Pay.
  • Xcode 13 or later.

Step 1: Setting Up Your Project

First, create a new Xcode project or open an existing one. Ensure your project is configured to support Apple Pay.

1.1 Enable Apple Pay Capability

  1. Open your project in Xcode.
  2. Select your project in the Project Navigator.
  3. Select your target.
  4. Go to Signing & Capabilities.
  5. Click the + Capability button.
  6. Search for and double-click Apple Pay.

1.2 Add Your Merchant ID

In the Apple Pay capability settings, add your Merchant ID that you registered in the Apple Developer account.

Adding Merchant ID

Replace 'https://i.imgur.com/YOUR_IMAGE_URL.png' with a valid image link that demonstrates adding the Merchant ID.

Step 2: Implementing Apple Pay in SwiftUI

Now, let’s create the necessary SwiftUI components and logic to initiate Apple Pay.

2.1 Create the PaymentRequest Struct

Define a struct that generates a PKPaymentRequest, which outlines the details of the payment.


import PassKit

struct PaymentRequest {
    func createPaymentRequest() -> PKPaymentRequest {
        let request = PKPaymentRequest()

        request.merchantIdentifier = "YOUR_MERCHANT_ID"
        request.countryCode = "US"
        request.currencyCode = "USD"
        request.supportedNetworks = [.visa, .masterCard, .amex]
        request.merchantCapabilities = .capability3DS
        request.paymentSummaryItems = [
            PKPaymentSummaryItem(label: "Product Name", amount: NSDecimalNumber(string: "10.00")),
            PKPaymentSummaryItem(label: "Shipping", amount: NSDecimalNumber(string: "2.00")),
            PKPaymentSummaryItem(label: "Tax", amount: NSDecimalNumber(string: "0.80")),
            PKPaymentSummaryItem(label: "Total", amount: NSDecimalNumber(string: "12.80"))
        ]

        return request
    }
}

Replace "YOUR_MERCHANT_ID" with your actual Merchant ID.

2.2 Create the ApplePayButton View

Create a SwiftUI view that presents the Apple Pay button and handles the payment process.


import SwiftUI
import PassKit

struct ApplePayButton: View {
    @State private var isPaymentAuthorized = false
    @State private var paymentStatus: PKPaymentAuthorizationStatus = .pending

    var body: some View {
        Representable()
            .frame(height: 44)
            .onChange(of: isPaymentAuthorized) { newValue in
                if newValue {
                    // Handle successful payment
                    print("Payment Authorized")
                } else {
                    // Handle payment failure
                    print("Payment Failed")
                }
            }
    }

    struct Representable: UIViewRepresentable {
        func makeUIView(context: Context) -> PKPaymentButton {
            return PKPaymentButton(paymentButtonType: .buy, paymentButtonStyle: .automatic)
        }

        func updateUIView(_ uiView: PKPaymentButton, context: Context) {
            // Update the view if needed
        }

        func makeCoordinator() -> Coordinator {
            Coordinator(self)
        }
    }

    class Coordinator: NSObject, PKPaymentAuthorizationViewControllerDelegate {
        var parent: Representable

        init(_ parent: Representable) {
            self.parent = parent
            super.init()
        }

        func paymentAuthorizationViewController(_ controller: PKPaymentAuthorizationViewController, didAuthorizePayment payment: PKPayment, handler completion: @escaping (PKPaymentAuthorizationResult) -> Void) {
            // Perform some server-side action, like charging the card
            let errors = [Error]()
            if errors.isEmpty {
                completion(PKPaymentAuthorizationResult(status: .success, errors: nil))
            } else {
                completion(PKPaymentAuthorizationResult(status: .failure, errors: errors))
            }
        }

        func paymentAuthorizationViewControllerDidFinish(_ controller: PKPaymentAuthorizationViewController) {
            controller.dismiss(animated: true, completion: nil)
        }
    }
}

This view incorporates UIViewRepresentable to utilize PKPaymentButton from UIKit. It also includes a coordinator to handle the delegation methods for the payment authorization process.

2.3 Integrating the ApplePayButton into Your SwiftUI View

Integrate the ApplePayButton into your SwiftUI view:


import SwiftUI

struct ContentView: View {
    var body: some View {
        VStack {
            Text("Ready to buy something?")
            ApplePayButton()
                .padding()
        }
    }
}

Step 3: Simulating Apple Pay in the Simulator or Testing on a Real Device

You can test your Apple Pay integration in the iOS Simulator or on a real device. To test in the simulator, you’ll need to add a test card in the Wallet app via Xcode.

3.1 Adding a Test Card

  1. In Xcode, navigate to Debug -> Simulate Payment Card.
  2. Choose a card type. This adds a test card to the Wallet in the simulator.

3.2 Testing on a Real Device

For testing on a real device, make sure you have a card added to the Wallet and that the device is signed in to your Apple ID.

Step 4: Handling Payment Authorization

Inside the Coordinator class, the paymentAuthorizationViewController delegate method is crucial for handling payment authorization. This is where you should:

  • Process the payment token from the payment object.
  • Communicate with your server to finalize the transaction.
  • Call the completion handler with a .success or .failure status.

Step 5: Finalizing the Transaction

After the user authorizes the payment and you’ve processed the payment with your backend, finalize the transaction by:

  • Displaying a success or failure message to the user.
  • Updating your app’s data to reflect the completed purchase.

Example Code: Full SwiftUI Implementation


import SwiftUI
import PassKit

struct ContentView: View {
    var body: some View {
        VStack {
            Text("Ready to buy something?")
            ApplePayButton()
                .padding()
        }
    }
}

struct ApplePayButton: View {
    @State private var isPaymentAuthorized = false
    @State private var paymentStatus: PKPaymentAuthorizationStatus = .pending

    var body: some View {
        Representable(isPaymentAuthorized: $isPaymentAuthorized, paymentStatus: $paymentStatus)
            .frame(height: 44)
            .onChange(of: isPaymentAuthorized) { newValue in
                if newValue {
                    // Handle successful payment
                    print("Payment Authorized")
                } else {
                    // Handle payment failure
                    print("Payment Failed")
                }
            }
    }

    struct Representable: UIViewRepresentable {
        @Binding var isPaymentAuthorized: Bool
        @Binding var paymentStatus: PKPaymentAuthorizationStatus

        func makeUIView(context: Context) -> PKPaymentButton {
            return PKPaymentButton(paymentButtonType: .buy, paymentButtonStyle: .automatic)
        }

        func updateUIView(_ uiView: PKPaymentButton, context: Context) {
            // Update the view if needed
        }

        func makeCoordinator() -> Coordinator {
            Coordinator(self)
        }
    }

    class Coordinator: NSObject, PKPaymentAuthorizationViewControllerDelegate {
        var parent: Representable

        init(_ parent: Representable) {
            self.parent = parent
            super.init()
        }

        func paymentAuthorizationViewController(_ controller: PKPaymentAuthorizationViewController, didAuthorizePayment payment: PKPayment, handler completion: @escaping (PKPaymentAuthorizationResult) -> Void) {
            // Perform some server-side action, like charging the card

            // Example: Mock successful payment after 3 seconds
            DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
                let errors: [Error] = [] // No errors for successful transaction
                if errors.isEmpty {
                    parent.paymentStatus = .success
                    completion(PKPaymentAuthorizationResult(status: .success, errors: nil))
                    parent.isPaymentAuthorized = true  // Payment was authorized
                } else {
                    parent.paymentStatus = .failure
                    completion(PKPaymentAuthorizationResult(status: .failure, errors: errors))
                    parent.isPaymentAuthorized = false  // Payment failed
                }
            }
        }

        func paymentAuthorizationViewControllerDidFinish(_ controller: PKPaymentAuthorizationViewController) {
            controller.dismiss(animated: true, completion: nil)
        }
    }
}

This comprehensive example integrates the ApplePayButton into a SwiftUI ContentView. The button’s state is managed using @State and @Binding properties, and it demonstrates how to handle payment authorization and completion using the PKPaymentAuthorizationViewControllerDelegate protocol.

Conclusion

Integrating Apple Pay into your SwiftUI application streamlines the purchasing process, improves user experience, and enhances transaction security. By following the steps outlined in this guide, you can enable Apple Pay in your app and provide your users with a seamless payment solution.