如何在Swift的Apple Pay代理方法中向Flutter返回支付状态?
问题描述
我开发了一款Flutter应用,用Swift原生实现Apple Pay功能,已完成Flutter到Swift的支付数据传递。ApplePayContext的didCompleteWith代理方法会返回支付状态("Success"、"Error"、"Cancelled"),但直接调用result(status)无法将状态传回Flutter,需要在代码标记//TODO return result to Flutter的位置实现状态回传。
原代码如下:
import Flutter import Foundation import PassKit import StripeApplePay import UIKit var itemName = "" var price = "" var freetrialid = -1 var deviceid = "-1" var optionId = 0 var startDate = "" @UIApplicationMain @objc class AppDelegate: FlutterAppDelegate, ObservableObject, ApplePayContextDelegate { static let backendAPIURL = URL( string: "xxxxxxxxxxx")! @Published var paymentStatus: STPApplePayContext.PaymentStatus? @Published var lastPaymentError: Error? override func application( _ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? ) -> Bool { let controller: FlutterViewController = window?.rootViewController as! FlutterViewController let channel = FlutterMethodChannel( name: "methodChannel", binaryMessenger: controller.binaryMessenger) channel.setMethodCallHandler { [unowned self] (methodCall, result) in if methodCall.method == "payWithApple" { guard let args = methodCall.arguments else { return } let myArgs = args as? [String: [String: Any]?] for key in myArgs!.keys { print(key) //not needed } for value in myArgs!.values { print(value as Any) itemName = value!["name"] as! String price = value!["price"] as! String freetrialid = value!["freetrialid"] as! Int deviceid = value!["deviceid"] as! String optionId = value!["optionId"] as! Int startDate = value!["startDate"] as! String } pay(itemName: itemName, price: price) } } GeneratedPluginRegistrant.register(with: self) return true } func pay(itemName: String, price: String) { print("== PAY ==") StripeAPI.defaultPublishableKey = "xxxxxxx" // Configure a payment request let pr = StripeAPI.paymentRequest( withMerchantIdentifier: "xxxxxxxxx", country: "AU", currency: "AUD") pr.paymentSummaryItems = [ PKPaymentSummaryItem(label: itemName, amount: NSDecimalNumber(string: price)) ] // Present the Apple Pay Context: let applePayContext = STPApplePayContext(paymentRequest: pr, delegate: self) applePayContext?.presentApplePay() } func applePayContext( _ context: STPApplePayContext, didCreatePaymentMethod paymentMethod: StripeAPI.PaymentMethod, paymentInformation: PKPayment, completion: @escaping STPIntentClientSecretCompletionBlock ) { // When the Apple Pay sheet is confirmed, create a PaymentIntent on your backend from the provided PKPayment information. getClientSecret { secret in if let clientSecret = secret["token"] { // Call the completion block with the PaymentIntent's client secret. completion(clientSecret as! String, nil) } else { completion(nil, NSError()) } } } func applePayContext( _ context: STPApplePayContext, didCompleteWith status: STPApplePayContext.PaymentStatus, error: Error? ) { switch status { case .success: // Payment succeeded, show a receipt view print("SUCCESS") // TODO return result to Flutter break case .error: // Payment failed, show the error print("ERROR") // TODO return result to Flutter break case .userCancellation: // User canceled the payment print("CANCELL") // TODO return result to Flutter break @unknown default: fatalError() } } private func getClientSecret(completion: @escaping ([String: Any]) -> Void) { let json: [String: Any] = [ "freetrialid": freetrialid, "deviceid": deviceid, "optionid": optionId, "start_date": startDate, "ishiit": false, "isflex": true, "islime": true, "freetrialapp": true, ] let jsonData = try? JSONSerialization.data(withJSONObject: json) let username = "xxxxxxxx" let password = "xxxxxxxx" let loginString = String(format: "%@:%@", username, password) let loginData = loginString.data(using: String.Encoding.utf8)! let base64LoginString = loginData.base64EncodedString() // create post request let url = URL( string: "xxxxxxxxx")! //PUT Your URL var request = URLRequest(url: url) request.httpMethod = "POST" request.setValue("application/json", forHTTPHeaderField: "Accept") request.setValue("application/json", forHTTPHeaderField: "Content-Type") request.addValue("Basic \(base64LoginString)", forHTTPHeaderField: "Authorization") // insert json data to the request request.httpBody = jsonData let task = URLSession.shared.dataTask(with: request) { data, response, error in guard let data = data, error == nil else { print(error?.localizedDescription ?? "No data") return } let responseJSON = try? JSONSerialization.jsonObject(with: data, options: []) if let responseJSON = responseJSON as? [String: Any] { print(responseJSON) //Code after Successfull POST Request completion(responseJSON) } } task.resume() } }
通常我会在pay(itemName: itemName, price: price)方法下返回值,但在此场景下无法实现。
解决方案
核心问题是Apple Pay支付流程是异步的,didCompleteWith代理方法会在支付结束后才触发,而原代码中的result是方法级变量,在payWithApple方法执行完后就会失效。需要把result保存为AppDelegate的实例变量,在代理方法中调用它传递状态。
修改步骤
- 在AppDelegate中添加可选的
FlutterResult实例变量,用于保存回调 - 在
payWithApple方法中,将传入的result赋值给该实例变量 - 在
didCompleteWith代理方法的各个case中,调用保存的result返回对应状态,调用完成后清空变量避免重复调用
修改后的完整代码
import Flutter import Foundation import PassKit import StripeApplePay import UIKit var itemName = "" var price = "" var freetrialid = -1 var deviceid = "-1" var optionId = 0 var startDate = "" @UIApplicationMain @objc class AppDelegate: FlutterAppDelegate, ObservableObject, ApplePayContextDelegate { static let backendAPIURL = URL( string: "xxxxxxxxxxx")! @Published var paymentStatus: STPApplePayContext.PaymentStatus? @Published var lastPaymentError: Error? // 新增:保存Flutter回调的实例变量 private var paymentResult: FlutterResult? override func application( _ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? ) -> Bool { let controller: FlutterViewController = window?.rootViewController as! FlutterViewController let channel = FlutterMethodChannel( name: "methodChannel", binaryMessenger: controller.binaryMessenger) channel.setMethodCallHandler { [unowned self] (methodCall, result) in if methodCall.method == "payWithApple" { guard let args = methodCall.arguments else { result(FlutterError(code: "INVALID_ARGS", message: "参数为空", details: nil)) return } let myArgs = args as? [String: [String: Any]?] guard let values = myArgs?.values.first, let valueDict = values else { result(FlutterError(code: "INVALID_ARGS", message: "参数格式错误", details: nil)) return } // 安全解析参数,避免强制解包崩溃 guard let name = valueDict["name"] as? String, let priceStr = valueDict["price"] as? String, let ftid = valueDict["freetrialid"] as? Int, let did = valueDict["deviceid"] as? String, let optId = valueDict["optionId"] as? Int, let sDate = valueDict["startDate"] as? String else { result(FlutterError(code: "INVALID_ARGS", message: "参数类型错误", details: nil)) return } itemName = name price = priceStr freetrialid = ftid deviceid = did optionId = optId startDate = sDate // 保存回调实例 self.paymentResult = result pay(itemName: itemName, price: price) } } GeneratedPluginRegistrant.register(with: self) return true } func pay(itemName: String, price: String) { print("== PAY ==") StripeAPI.defaultPublishableKey = "xxxxxxx" let pr = StripeAPI.paymentRequest( withMerchantIdentifier: "xxxxxxxxx", country: "AU", currency: "AUD") pr.paymentSummaryItems = [ PKPaymentSummaryItem(label: itemName, amount: NSDecimalNumber(string: price)) ] let applePayContext = STPApplePayContext(paymentRequest: pr, delegate: self) applePayContext?.presentApplePay() } func applePayContext( _ context: STPApplePayContext, didCreatePaymentMethod paymentMethod: StripeAPI.PaymentMethod, paymentInformation: PKPayment, completion: @escaping STPIntentClientSecretCompletionBlock ) { getClientSecret { secret in if let clientSecret = secret["token"] { completion(clientSecret as! String, nil) } else { completion(nil, NSError(domain: "PAYMENT_DOMAIN", code: -1, userInfo: [NSLocalizedDescriptionKey: "获取支付凭证失败"])) } } } func applePayContext( _ context: STPApplePayContext, didCompleteWith status: STPApplePayContext.PaymentStatus, error: Error? ) { defer { // 调用完成后清空回调,避免重复调用 paymentResult = nil } guard let result = paymentResult else { return } switch status { case .success: print("SUCCESS") result("Success") break case .error: print("ERROR") if let err = error { result(FlutterError(code: "PAYMENT_FAILED", message: err.localizedDescription, details: nil)) } else { result(FlutterError(code: "PAYMENT_FAILED", message: "支付失败", details: nil)) } break case .userCancellation: print("CANCELL") result("Cancelled") break @unknown default: fatalError() } } private func getClientSecret(completion: @escaping ([String: Any]) -> Void) { let json: [String: Any] = [ "freetrialid": freetrialid, "deviceid": deviceid, "optionid": optionId, "start_date": startDate, "ishiit": false, "isflex": true, "islime": true, "freetrialapp": true, ] let jsonData = try? JSONSerialization.data(withJSONObject: json) let username = "xxxxxxxx" let password = "xxxxxxxx" let loginString = String(format: "%@:%@", username, password) let loginData = loginString.data(using: String.Encoding.utf8)! let base64LoginString = loginData.base64EncodedString() let url = URL(string: "xxxxxxxxx")! var request = URLRequest(url: url) request.httpMethod = "POST" request.setValue("application/json", forHTTPHeaderField: "Accept") request.setValue("application/json", forHTTPHeaderField: "Content-Type") request.addValue("Basic \(base64LoginString)", forHTTPHeaderField: "Authorization") request.httpBody = jsonData let task = URLSession.shared.dataTask(with: request) { data, response, error in guard let data = data, error == nil else { print(error?.localizedDescription ?? "No data") completion([:]) return } let responseJSON = try? JSONSerialization.jsonObject(with: data, options: []) if let responseJSON = responseJSON as? [String: Any] { print(responseJSON) completion(responseJSON) } else { completion([:]) } } task.resume() } }
额外优化点
- 增加参数安全解析逻辑,避免强制解包导致崩溃
- 错误场景返回
FlutterError,让Flutter端能捕获具体错误信息 - 使用
defer语句确保回调调用后清空实例变量,防止内存泄漏或重复调用
内容的提问来源于stack exchange,提问作者Mikhail
相关产品推荐
相关产品推荐

