Внедрение Apple Pay с SwiftUI

Я впервые работаю с PassKit и SwiftUI над большим проектом. Я пытаюсь внедрить Apple Pay SwiftUI, и, поскольку для этого пока нет собственного способа, я попытался обернуть PKPaymentAuthorizationViewController в UIViewControllerRepresentable, но я не уверен, правильно ли я это делаю.

Вид отображается правильно и, кажется, работает, если щелкнуть по нему для оплаты. Я управляю отображением окна, привязывая представление к логическому объекту isPresentingApplePay (см. Ниже). Проблемы возникают, когда окно следует закрыть. Нажатие на кнопку cancel не закрывает представление; иногда он даже не вызывает функцию делегата paymentAuthorizationViewControllerDidFinish. То же самое происходит после отправки платежа. Иногда вызывается делегат didFinish, но представление не закрывается. Я попытался передать переменную привязки isPresentingApplePay и установить для нее значение false из didFinish, но она ничего не делает. Единственный способ заставить представление исчезнуть - это нажать любую часть за пределами окна оплаты Apple.

Кто-нибудь знает, что я делаю не так? Что-то мне совсем не хватает?

Я получаю правильное отображение окна оплаты Apple при нажатии кнопки, привязав вид к 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)
}

person user2805119    schedule 19.03.2020    source источник
comment
Вы когда-нибудь находили решение этой проблемы? Сам пытаюсь разобраться ...   -  person FIGBERT    schedule 03.05.2020
comment
Я еще не. Я займусь этим на этой неделе, и я отправлю все, что найду   -  person user2805119    schedule 04.05.2020
comment
@ user2805119 Привет, вы нашли собственный способ SwiftUI?   -  person    schedule 25.06.2020
comment
Чтобы продемонстрировать, как представить кнопку Apple Pay в приложении SwiftUI, загрузите проект примера кода Fruta. В частности, посмотрите файл Fruta ›Shared› Orders ›PaymentButton.swift. - Автор сообщения Apple Documentation Engineer.   -  person SHS    schedule 30.08.2020
comment
Приложение Fruta только обертывает кнопку пользовательского интерфейса, но не иллюстрирует отображение / отклонение представления авторизации или управления состоянием.   -  person TruMan1    schedule 19.09.2020


Ответы (4)


У меня возникла аналогичная проблема, которую я решил, используя другой подход, не взаимодействующий с 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)
    }
}

}

Наконец, он отлично работает

person Taif Al Musabe    schedule 12.05.2020
comment
paymentController? .present (завершение: похоже, не работает, как это возможно, что вам не нужно передавать VC для презентации? - person Lucas van Dongen; 29.06.2020
comment
@DepartamentoB PKPaymentAuthorizationController не является ViewController, поэтому он работает. - person Petr Syrov; 22.09.2020
comment
Большое спасибо, это сводило меня с ума !! - person JMK; 13.06.2021

Версию, которая работает для представления PKPaymentAuthorizationViewController, можно найти здесь: Как правильно представить PKPaymentAuthorizationViewController в проекте только с SwiftUI?

Обертка, которую я использую, выглядит следующим образом:

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

person Lucas van Dongen    schedule 02.07.2020

Вам даже не нужно использовать UIViewControllerRepresentable или UIKit в SwiftUI. Используйте 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

person TruMan1    schedule 16.10.2020

Принятый ответ Таифа очень хорош со стороны 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()
    }
}
person IcyHovercraft    schedule 20.10.2020