使用 SwiftUI 实现 Apple Pay
Posted
技术标签:
【中文标题】使用 SwiftUI 实现 Apple Pay【英文标题】:Implementing Apple Pay with SwiftUI 【发布时间】:2020-03-19 23:05:25 【问题描述】:这是我第一次在大型项目中使用 PassKit 和 SwiftUI。我正在尝试实现 Apple Pay SwiftUI,但由于还没有本地方法可以做到这一点,我尝试将 PKPaymentAuthorizationViewController 包装在 UIViewControllerRepresentable 中,但我不确定我是否做得正确。
该视图显示正确,并且在单击它进行支付时似乎可以正常工作。我通过将视图绑定到 isPresentingApplePay 布尔值来控制显示窗口(见下文)。当窗口应该被关闭时,问题就会发生。点击cancel
按钮不会关闭视图;有时它甚至不调用 paymentAuthorizationViewControllerDidFinish 委托函数。 在提交付款后也会发生同样的事情。有时会调用 didFinish 委托,但不会关闭视图。我尝试传递绑定变量 isPresentingApplePay 并从 didFinish 将其设置为 false 但它没有做任何事情。让视图消失的唯一方法是点击苹果支付窗口外部的任何部分。
有谁知道我做错了什么?有什么我完全想念的吗?
通过在if statement
下绑定视图,我可以在按下按钮时正确显示苹果支付窗口
这是我的 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)
【问题讨论】:
您找到解决方案了吗?我正在尝试自己解决... 我还没有。我这周要解决这个问题,我会发回我发现的任何东西 @user2805119 嗨,你找到原生 SwiftUI 方式了吗? 如需演示如何在 SwiftUI 应用中显示Apple Pay 按钮,请下载Fruta 示例代码项目。特别是,查看文件 Fruta > Shared > Orders > PaymentButton.swift。 - 由 Apple 文档工程师发布。 Fruta 应用程序仅包装了 UI 按钮,但没有说明呈现/关闭授权视图或状态管理。 【参考方案1】:我遇到了类似的问题,然后我通过使用另一种不与 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 弹出窗口使用了以下 Button 代码。
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)
终于可以正常使用了
【讨论】:
paymentController?.present(completion: 好像不行,怎么可能不需要通过VC来呈现? @DepartamentoB PKPaymentAuthorizationController 不是 ViewController,所以它可以工作。 非常感谢,这让我精神崩溃了!!【参考方案2】:可在此处找到适用于呈现PKPaymentAuthorizationViewController
的版本:How to properly present PKPaymentAuthorizationViewController in a SwiftUI only project?
我使用的包装如下:
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
,您需要将此添加到struct
的字段中let request: PKPaymentRequest
之后,然后在创建PKPaymentAuthorizationViewController
时设置委托
【讨论】:
【参考方案3】:您甚至不需要在 SwiftUI 中使用 UIViewControllerRepresentable
或 UIKit
。使用PKPaymentAuthorizationController
,而不是view controller version:
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
【讨论】:
【参考方案4】: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()
【讨论】:
以上是关于使用 SwiftUI 实现 Apple Pay的主要内容,如果未能解决你的问题,请参考以下文章
使用来自 Kotlin/Native 和 Gradle 的 Apple 依赖项,更具体地说是 SwiftUI
在 SwiftUI 中使用 WCSession 向 Apple Watch 发送消息