这是我第一次在大型项目中使用PassKit和SwiftUI。我正在尝试实现Apple Pay SwiftUI,由于还没有原生的方法,我尝试将PKPaymentAuthorizationViewController封装在UIViewControllerRepresentable中,但我不确定我是否做得正确。
该视图显示正确,并且在单击它进行支付时似乎可以工作。我通过将视图绑定到isPresentingApplePay布尔来控制窗口的显示(见下文(。当窗口应该被关闭时,问题就会出现。点击cancel
按钮不会关闭视图;有时它甚至不调用paymentAuthorizationViewControllerDidFinish委托函数。同样的事情发生在提交付款后。有时会调用didFinish委托,但该视图不会被驳回。我尝试从didFinish传递绑定变量isPresentingApplePay,并将其设置为false,但它什么也没做。让视图消失的唯一方法是点击苹果支付窗口外的任何部分。
有人知道我做错了什么吗?有什么我完全不知道的吗?
我通过将视图绑定到if statement
下,使apple pay窗口在按下按钮时正确显示
这是我的PKPaymentAuthorizationViewController包装:
import Foundation
import PassKit
import SwiftUI
struct ApplePayController: UIViewControllerRepresentable {
@Environment(.presentationMode) var presentationMode
@EnvironmentObject var userData: UserData
@Binding var purchase: Purchase
@Binding var isPresenting: Bool
let items: [PKPaymentSummaryItem]
func updateUIViewController(_ uiViewController: PKPaymentAuthorizationViewController, context: Context) {
}
typealias UIViewControllerType = PKPaymentAuthorizationViewController
func makeUIViewController(context: Context) -> PKPaymentAuthorizationViewController {
let applePayManager = ApplePayManager(items: items)
let apm = applePayManager.paymentViewController()!
apm.delegate = context.coordinator
return apm
}
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
class Coordinator: NSObject, PKPaymentAuthorizationViewControllerDelegate {
var parent: ApplePayController
init(_ parent: ApplePayController) {
self.parent = parent
}
func paymentAuthorizationViewControllerDidFinish(_ controller: PKPaymentAuthorizationViewController) {
controller.dismiss(animated: true) {
self.parent.isPresenting = false
}
}
func paymentAuthorizationViewController(_ controller: PKPaymentAuthorizationViewController, didAuthorizePayment payment: PKPayment, handler completion: @escaping (PKPaymentAuthorizationResult) -> Void) {
print("did authorize payment")
}
func paymentAuthorizationViewControllerWillAuthorizePayment(_ controller: PKPaymentAuthorizationViewController) {
print("Will authorize payment")
}
}
class ApplePayManager: NSObject {
let currencyCode: String
let countryCode: String
let merchantID: String
let paymentNetworks: [PKPaymentNetwork]
let items: [PKPaymentSummaryItem]
init(items: [PKPaymentSummaryItem],
currencyCode: String = "USD",
countryCode: String = "US",
merchantID: String = "xxx.merchant.xxx",
paymentNetworks: [PKPaymentNetwork] = [PKPaymentNetwork.amex, PKPaymentNetwork.masterCard, PKPaymentNetwork.visa]) {
self.items = items
self.currencyCode = currencyCode
self.countryCode = countryCode
self.merchantID = merchantID
self.paymentNetworks = paymentNetworks
}
func paymentViewController() -> PKPaymentAuthorizationViewController? {
if PKPaymentAuthorizationViewController.canMakePayments(usingNetworks: paymentNetworks) {
let request = PKPaymentRequest()
request.currencyCode = self.currencyCode
request.countryCode = self.countryCode
request.supportedNetworks = paymentNetworks
request.merchantIdentifier = self.merchantID
request.paymentSummaryItems = items
request.merchantCapabilities = [.capabilityCredit, .capabilityDebit]
return PKPaymentAuthorizationViewController(paymentRequest: request)
}
return nil
}
}
}
这就是我在UIView:中显示它的方式
if isPresentingApplePay {
ApplePayController(purchase: self.$currentOrder.purchase, isPresenting: $isPresentingApplePay, items: self.createOrder(with: self.currentOrder.purchase)).environmentObject(self.userData)
}
我也遇到过类似的问题,然后我通过使用另一种不与UIViewControllerRepresentable接口的方法解决了这个问题。相反,我创建了一个单独的类来处理Apple Pay,就像下面的一样
import PassKit
typealias PaymentCompletionHandler = (Bool) -> Void
class PaymentHandler: NSObject {
static let supportedNetworks: [PKPaymentNetwork] = [
.amex,
.masterCard,
.visa
]
var paymentController: PKPaymentAuthorizationController?
var paymentSummaryItems = [PKPaymentSummaryItem]()
var paymentStatus = PKPaymentAuthorizationStatus.failure
var completionHandler: PaymentCompletionHandler?
func startPayment(completion: @escaping PaymentCompletionHandler) {
let amount = PKPaymentSummaryItem(label: "Amount", amount: NSDecimalNumber(string: "8.88"), type: .final)
let tax = PKPaymentSummaryItem(label: "Tax", amount: NSDecimalNumber(string: "1.12"), type: .final)
let total = PKPaymentSummaryItem(label: "ToTal", amount: NSDecimalNumber(string: "10.00"), type: .pending)
paymentSummaryItems = [amount, tax, total];
completionHandler = completion
// Create our payment request
let paymentRequest = PKPaymentRequest()
paymentRequest.paymentSummaryItems = paymentSummaryItems
paymentRequest.merchantIdentifier = "merchant.com.YOURDOMAIN.YOURAPPNAME"
paymentRequest.merchantCapabilities = .capability3DS
paymentRequest.countryCode = "US"
paymentRequest.currencyCode = "USD"
paymentRequest.requiredShippingContactFields = [.phoneNumber, .emailAddress]
paymentRequest.supportedNetworks = PaymentHandler.supportedNetworks
// Display our payment request
paymentController = PKPaymentAuthorizationController(paymentRequest: paymentRequest)
paymentController?.delegate = self
paymentController?.present(completion: { (presented: Bool) in
if presented {
NSLog("Presented payment controller")
} else {
NSLog("Failed to present payment controller")
self.completionHandler!(false)
}
})
}
}
/*
PKPaymentAuthorizationControllerDelegate conformance.
*/
extension PaymentHandler: PKPaymentAuthorizationControllerDelegate {
func paymentAuthorizationController(_ controller: PKPaymentAuthorizationController, didAuthorizePayment payment: PKPayment, completion: @escaping (PKPaymentAuthorizationStatus) -> Void) {
// Perform some very basic validation on the provided contact information
if payment.shippingContact?.emailAddress == nil || payment.shippingContact?.phoneNumber == nil {
paymentStatus = .failure
} else {
// Here you would send the payment token to your server or payment provider to process
// Once processed, return an appropriate status in the completion handler (success, failure, etc)
paymentStatus = .success
}
completion(paymentStatus)
}
func paymentAuthorizationControllerDidFinish(_ controller: PKPaymentAuthorizationController) {
controller.dismiss {
DispatchQueue.main.async {
if self.paymentStatus == .success {
self.completionHandler!(true)
} else {
self.completionHandler!(false)
}
}
}
}
}
并使用以下按钮代码为Apple Pay弹出窗口。
struct ContentView: View {
let paymentHandler = PaymentHandler()
var body: some View {
Button(action: {
self.paymentHandler.startPayment { (success) in
if success {
print("Success")
} else {
print("Failed")
}
}
}, label: {
Text("PAY WITH APPLE")
.font(Font.custom("HelveticaNeue-Bold", size: 16))
.padding(10)
.foregroundColor(.white)
}
}
}
最后,它工作良好
Taif接受的答案在PaymentHandler
方面非常好。
但使用实际的Apple Pay按钮,听起来你遇到了和我完全一样的问题!基本上,你必须把你的UIViewRepresentable
放在ButtonStyle
中,然后用它来设计SwiftUI按钮的样式。我不确定为什么这是真的,但下面是它应该工作的代码:
import SwiftUI
import UIKit
import PassKit
struct PaymentButton: View {
let paymentHandler = PaymentHandler() //As defined by Taif
var body: some View {
Button(action: {
// Using the code from Tarif!
self.paymentHandler.startPayment { success in
if success {
print("Success")
} else {
print("Failed")
}
}
}, 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) { }
}
然后使用它,你会说
struct ContentView: View {
var body: some View {
PaymentButton()
}
}
可在此处找到用于呈现PKPaymentAuthorizationViewController
的版本:如何在仅SwiftUI的项目中正确呈现PKPaymentAuthorizationViewController?
我使用的包装看起来如下:
import PassKit
import SwiftUI
struct ApplePayWrapper: UIViewControllerRepresentable {
typealias UIViewControllerType = PKPaymentAuthorizationViewController
let request: PKPaymentRequest
func makeUIViewController(context: Context) -> PKPaymentAuthorizationViewController {
let applePayController = PKPaymentAuthorizationViewController(paymentRequest: request)
return applePayController!
}
func updateUIViewController(_ uiViewController: PKPaymentAuthorizationViewController, context: Context) {
// Nothing
}
}
我使用ViewModel
作为delegate
,您需要将其添加到let request: PKPaymentRequest
之后的struct
字段中,然后在创建PKPaymentAuthorizationViewController
时设置委托
您甚至不需要在SwiftUI中使用UIViewControllerRepresentable
或UIKit
。使用PKPaymentAuthorizationController
,而不是视图控制器版本:
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
}
https://developer.apple.com/forums/thread/649662?answerId=641228022#641228022