Apple Pay with SwiftUI

Hi, this is kind of a myth right now since the documentation is like a ghost. How can I integrate Apple Pay on my SwiftUI app? Is there any guide/doc or useful Simple code to review?

Thanks
Answered by Systems Engineer in 613169022
Right, the sample code for this is lacking, so I would open a bug report for examples of how this done. The thing to remember about Apple Pay is that there are Human Interface Guidelines on how buttons are displayed. So in SwiftUI when creating these buttons, just keep that in mind.

Code Block swift
struct ApplePayButton: UIViewRepresentable {
func updateUIView(_ uiView: PKPaymentButton, context: Context) {
}
func makeUIView(context: Context) -> PKPaymentButton {
return PKPaymentButton(paymentButtonType: .plain, paymentButtonStyle: .black)
}
}
struct ApplePayButtonStyle: ButtonStyle {
func makeBody(configuration: Self.Configuration) -> some View {
return ApplePayButton()
}
}
Button( action: {
self.setupPKPaymentRequest()
}, label: { Text("")} )
.frame(width: 212, height: 38, alignment: .center)
.buttonStyle(ApplePayButtonStyle()


Because code snippets like the one I put together above uses an empty label: Text(""), which, is not great, however just adding a text label that says Apple Pay on it also conflicts with the Human Interface Guideline. So please mention things like things like this in the bug report you create and follow up with the feedback id so I can copy myself on it.


Matt Eaton
DTS Engineering, CoreOS
There's not a built-in way to do this in SwiftUI, so a lot of people have used HostingControllers and UIViewControllerRepresentable to implement Apple Pay. To try this in actual SwiftUI code, check the answer on this stack overflow question and let me know if it helps you out

Accepted Answer
Right, the sample code for this is lacking, so I would open a bug report for examples of how this done. The thing to remember about Apple Pay is that there are Human Interface Guidelines on how buttons are displayed. So in SwiftUI when creating these buttons, just keep that in mind.

Code Block swift
struct ApplePayButton: UIViewRepresentable {
func updateUIView(_ uiView: PKPaymentButton, context: Context) {
}
func makeUIView(context: Context) -> PKPaymentButton {
return PKPaymentButton(paymentButtonType: .plain, paymentButtonStyle: .black)
}
}
struct ApplePayButtonStyle: ButtonStyle {
func makeBody(configuration: Self.Configuration) -> some View {
return ApplePayButton()
}
}
Button( action: {
self.setupPKPaymentRequest()
}, label: { Text("")} )
.frame(width: 212, height: 38, alignment: .center)
.buttonStyle(ApplePayButtonStyle()


Because code snippets like the one I put together above uses an empty label: Text(""), which, is not great, however just adding a text label that says Apple Pay on it also conflicts with the Human Interface Guideline. So please mention things like things like this in the bug report you create and follow up with the feedback id so I can copy myself on it.


Matt Eaton
DTS Engineering, CoreOS
For a demonstration of how to present an Apple Pay button in your SwiftUI app, please download the Fruta sample code project released today. In particular, check out the file Fruta > Shared > Orders > PaymentButton.swift.
Turns out you don't even need to use UIViewControllerRepresentable, or UIKit at all in SwiftUI! The trick is to use PKPaymentAuthorizationController, NOT the view controller version.

From the docs:

The PKPaymentAuthorizationController class performs the same role as the PKPaymentAuthorizationViewController class, but it does not depend on the UIKit framework. This means that the authorization controller can be used in places where a view controller cannot (for example, in watchOS apps or in SiriKit extensions).

Apply Pay integration on SwiftUI should be approached more like a watchOS or Siri integration than a UIKit app.

Below is a sample of the implementation:

Code Block
let request = PKPaymentRequest()
request.paymentSummaryItems = ...
let controller = PKPaymentAuthorizationController(paymentRequest: request)
controller.delegate = self
controller.present { [weak self] presented in
// Apple Pay presented from scene window
}


The controller automatically finds the scene window to present from. No more porting to UIKit or dismissal memory leaks!
This took me FOREVER to figure out, so maybe someone will find this helpful:

Basically, it's not enough to just wrap the button in a UIViewRepresentable. You have to put that in a ButtonStyle, and then style a SwiftUI button with it. If you don't, it seems like your payment sheet will break! I'm not sure why this is true, but here's the code as it should work:

Code Block swift
import SwiftUI
import UIKit
import PassKit
struct PaymentButton: View {
var body: some View {
Button(action: {
/* Your custom payment code here */
}, label: { EmptyView() } )
.buttonStyle(PaymentButtonStyle())
}
}
struct PaymentButtonStyle: ButtonStyle {
func makeBody(configuration: Self.Configuration) -> some View {
return PaymentButtonHelper()
}
}  
struct PaymentButtonHelper: View {
var body: some View {
PaymentButtonRepresentable()
.frame(minWidth: 100, maxWidth: 400)
.frame(height: 60)
.frame(maxWidth: .infinity)
}
}
extension PaymentButtonHelper {
struct PaymentButtonRepresentable: UIViewRepresentable {
     
var button: PKPaymentButton {
let button = PKPaymentButton(paymentButtonType: .buy, paymentButtonStyle: .black) /*customize here*/
button.cornerRadius = 4.0 /* also customize here */
return button
}
     
func makeUIView(context: Context) -> PKPaymentButton {
return button
}
func updateUIView(_ uiView: PKPaymentButton, context: Context) { }
}
}


You can also use the button entirely in SwiftUI using iPaymentButton. Example usage would look like:

Code Block swift
import SwiftUI
import iPaymentButton
struct ContentView: View {
var body: some View {
iPaymentButton(type: .support, style: .whiteOutline, action: {
/* Add your custom payment code here */
})
}
}

Import instructions on iSwiftUI 👉

Apple Pay with SwiftUI
 
 
Q