【问题标题】:Stripe Error: "Should not handle multiple payments at once."条纹错误:“不应一次处理多笔付款。”
【发布时间】:2021-07-08 16:46:50
【问题描述】:

我正在尝试确认由我的后端 api 生成的 setupintent,但我认为它会尝试多次提交 setupintent。以下方法用于使用 Stripe iOS SDK(不幸的是为 uikit 制作)向用户添加新卡。我调整了示例方法并将它们包装在一些 uiviewrepresentables 中,以便在必要时可以使用 3DS 弹出窗口。问题是它在 Stripe 中得到了验证。 SetupIntent 出现在日志中,但应用程序崩溃。我将在下面为其他可能在 Swiftui 上实现 Stripe SDK 遇到困难的人分享我的代码。

这是我使用 STPPaymentMethod 参数检索并确认 setupintent 的一段代码:

struct FakeStripeView: UIViewControllerRepresentable {
    @EnvironmentObject var navView : NavView
    @Binding var cardParams : STPPaymentMethodCardParams
    @Binding var isActive : Bool
    @Binding var billing : STPPaymentMethodBillingDetails
    
    public typealias UIViewControllerType = FakeStripeViewController
    
    func makeUIViewController(context: UIViewControllerRepresentableContext<FakeStripeView>) -> FakeStripeViewController {
        let viewController = FakeStripeViewController(navView: navView)
        return viewController
    }
    
    func updateUIViewController(_ uiViewController: FakeStripeViewController, context _: UIViewControllerRepresentableContext<FakeStripeView>) {
        if navView.isActive{
            uiViewController.createSetupIntent(cardParams: cardParams, billing: billing, completion: { state in
                if state != false{
                    self.navView.isActive = false
                    self.navView.addNewCard = false
                }
            })
        }
    }
}

class FakeStripeViewController: UIViewController {
    
    var navView: NavView?
        
        convenience init() {
            self.init(navView: nil)
        }
        
        init(navView: NavView?) {
            self.navView = navView
            super.init(nibName: nil, bundle: nil)
        }
        
        required init?(coder aDecoder: NSCoder) {
            super.init(coder: aDecoder)
    }
    
    func createSetupIntent(cardParams: STPPaymentMethodCardParams, billing: STPPaymentMethodBillingDetails, completion: @escaping (Bool) -> ()) {
        StripeAPIClient.shared.createSetupIntent(completion: { (setupIntent) in
            if(setupIntent.status != "not completed"){
                if(setupIntent.status.contains("error:")){
                    print(setupIntent.status)
                }
                else{
                    let paymentMethodParams = STPPaymentMethodParams(card: cardParams, billingDetails: billing, metadata: nil)
                    let setupIntentParams = STPSetupIntentConfirmParams(clientSecret: setupIntent.client_secret)
                    setupIntentParams.paymentMethodParams = paymentMethodParams
                    
                    let paymentHandler = STPPaymentHandler.shared()
                    paymentHandler.confirmSetupIntent(setupIntentParams, with: self) { status, setupIntent, error in
                                switch (status) {
                                case .failed:
                                    // Setup failed
                                    break
                                case .canceled:
                                    // Setup canceled
                                    break
                                case .succeeded:
                                    print("successful")
                                    StripeAPIClient.shared.retrievePaymentMethods()
                                    completion(true)
                                    self.navView?.isActive = false
                                    self.navView?.addNewCard = false
                                    break
                                @unknown default:
                                    fatalError()
                                    break
                                }
                            }
                        }
                    }
                })
            }
        }

extension FakeStripeViewController: STPAuthenticationContext {
    func authenticationPresentingViewController() -> UIViewController {
        self
    }
}

这是卡片字段的包装器:

struct StripePaymentCardTextField: UIViewRepresentable {
    
    @Binding var cardParams: STPPaymentMethodCardParams
    @Binding var isValid: Bool
    @Binding var billing : STPPaymentMethodBillingDetails
    
    func makeUIView(context: Context) -> STPPaymentCardTextField {
        let input = STPPaymentCardTextField()
        input.borderColor = getColorFromHex(hex: 0x7653DB, alpha: 1.0)
        input.borderWidth = 5
        input.cornerRadius = 15
        input.delegate = context.coordinator
        return input
    }
    
    func makeCoordinator() -> StripePaymentCardTextField.Coordinator { Coordinator(self) }

    func updateUIView(_ view: STPPaymentCardTextField, context: Context) { }
    
    class Coordinator: NSObject, STPPaymentCardTextFieldDelegate {

        var parent: StripePaymentCardTextField
        
        init(_ textField: StripePaymentCardTextField) {
            parent = textField
            parent.billing.email = UserData.shared.user?.email
            parent.billing.name = UserData.shared.user?.name
            parent.billing.phone = UserData.shared.user?.phone
        }
        
        func paymentCardTextFieldDidChange(_ textField: STPPaymentCardTextField) {
            parent.cardParams = textField.cardParams
            parent.isValid = textField.isValid
            parent.billing.address?.postalCode = textField.postalCode
        }
    }
}

这是添加支付方式的视图。在这个视图中,fakestripeview 被实现,因此我们可以在需要时弹出 3ds。

struct AddPaymentMethod: View {
    @State var cardParams : STPPaymentMethodCardParams = STPPaymentMethodCardParams()
    @State var billingParams : STPPaymentMethodBillingDetails = STPPaymentMethodBillingDetails()
    @State var isValid = false
    @EnvironmentObject var navView : NavView
    
    @State var showAlert = false
    var body: some View {
        VStack(alignment: .center){
            CCView(cardParams: $cardParams)
                .padding(.leading,10)
                .frame(maxWidth: .infinity, alignment: .center)
                .frame(height: UIScreen.main.bounds.width/2)
                .padding(.bottom)
            
            
                
            
            StripePaymentCardTextField(cardParams: $cardParams, isValid: $isValid, billing: $billingParams)
                .frame(height:70)
                .padding()

            Button(action: {
                if(isValid){
                    navView.isActive = true
                }
                else{
                    self.showAlert = true
                }
                
            }, label: {
                Text("Submit")
                    .foregroundColor(.white)
                    .frame(height: 50)
                    .frame(maxWidth: .infinity)
                    .background(beatactViolet)
                    .modifier(EventCardModifier())
                    .padding()
                    
            })
            Spacer()
            FakeStripeView(cardParams: $cardParams, isActive: $navView.isActive, billing: $billingParams)
                .frame(width: 0, height: 0).environmentObject(NavView.shared)
        }.alert(isPresented: $showAlert, content: {
            Alert(title: Text("Card/Adress Invalid"), message: Text("Please try again."), dismissButton: .default(Text("OK!")))
        }).navigationTitle("Add card")
    }
}

我认为这种方法的主要问题是 fakestripeview 和 AddPaymentMethod 视图的方法之间的接口。如果它对您有任何帮助,请随时使用任何此代码。我知道我花了一段时间才想出一个可行的方法来做到这一点。这种方法有效,但在确认 setupintent 后,它会使应用程序崩溃(但至少它以条带显示)。关于我如何能够改进这一点并使其发挥作用的任何线索?

【问题讨论】:

    标签: ios swift swiftui stripe-payments


    【解决方案1】:

    对于任何对此感兴趣的人:

    struct FakeStripeView: UIViewControllerRepresentable {
        @EnvironmentObject var navView : NavView
        @Binding var cardParams : STPPaymentMethodCardParams
        @Binding var isActive : Bool
        @Binding var billing : STPPaymentMethodBillingDetails
        
        public typealias UIViewControllerType = FakeStripeViewController
        
        func makeUIViewController(context: UIViewControllerRepresentableContext<FakeStripeView>) -> FakeStripeViewController {
            let viewController = FakeStripeViewController.shared
            return viewController
        }
        
        func updateUIViewController(_ uiViewController: FakeStripeViewController, context _: UIViewControllerRepresentableContext<FakeStripeView>) {}
    }
    
    class FakeStripeViewController: UIViewController {
        
        static let shared = FakeStripeViewController()
        var navView: NavView?
            
            convenience init() {
                self.init(navView: nil)
            }
            
            init(navView: NavView?) {
                self.navView = navView
                super.init(nibName: nil, bundle: nil)
            }
            
            required init?(coder aDecoder: NSCoder) {
                super.init(coder: aDecoder)
        }
        
        func createSetupIntent(cardParams: STPPaymentMethodCardParams, billing: STPPaymentMethodBillingDetails, completion: @escaping (Bool) -> ()) {
            StripeAPIClient.shared.createSetupIntent(completion: { (setupIntent) in
                if(setupIntent.status != "not completed"){
                    if(setupIntent.status.contains("error:")){
                        print(setupIntent.status)
                    }
                    else{
                        let paymentMethodParams = STPPaymentMethodParams(card: cardParams, billingDetails: billing, metadata: nil)
                        let setupIntentParams = STPSetupIntentConfirmParams(clientSecret: setupIntent.client_secret)
                        setupIntentParams.paymentMethodParams = paymentMethodParams
                        
                        let paymentHandler = STPPaymentHandler.shared()
                        paymentHandler.confirmSetupIntent(setupIntentParams, with: self) { status, setupIntent, error in
                                    switch (status) {
                                    case .failed:
                                        // Setup failed
                                        break
                                    case .canceled:
                                        // Setup canceled
                                        break
                                    case .succeeded:
                                        print("successful")
                                        StripeAPIClient.shared.retrievePaymentMethods()
                                        completion(true)
                                        self.navView?.isActive = false
                                        self.navView?.addNewCard = false
                                        break
                                    @unknown default:
                                        fatalError()
                                        break
                                    }
                                }
                            }
                        }
                    })
                }
            }
    
    extension FakeStripeViewController: STPAuthenticationContext {
        func authenticationPresentingViewController() -> UIViewController {
            self
        }
    }
    

    我添加了一个可以在 swiftui 视图中调用的类的共享实例。比如这样:

    
    struct AddPaymentMethod: View {
        @State var cardParams : STPPaymentMethodCardParams = STPPaymentMethodCardParams()
        @State var billingParams : STPPaymentMethodBillingDetails = STPPaymentMethodBillingDetails()
        @State var isValid = false
        @EnvironmentObject var navView : NavView
        
        
        @State var showAlert = false
        var body: some View {
            VStack(alignment: .center){
                CCView(cardParams: $cardParams)
                    .padding(.leading,10)
                    .frame(maxWidth: .infinity, alignment: .center)
                    .frame(height: UIScreen.main.bounds.width/2)
                    .padding(.bottom)
                
                
                    
                
                StripePaymentCardTextField(cardParams: $cardParams, isValid: $isValid, billing: $billingParams)
                    .frame(height:70)
                    .padding()
    
                Button(action: {
                    if(isValid){
                        FakeStripeViewController.shared.createSetupIntent(cardParams: cardParams, billing: billingParams, completion: { state in
                            if(state){
                                navView.addNewCard = false
                            }
                        })
                    }
                    else{
                        self.showAlert = true
                    }
                    
                }, label: {
                    Text("Submit")
                        .foregroundColor(.white)
                        .frame(height: 50)
                        .frame(maxWidth: .infinity)
                        .background(beatactViolet)
                        .modifier(EventCardModifier())
                        .padding()
                        
                })
                Spacer()
                FakeStripeView(cardParams: $cardParams, isActive: $navView.isActive, billing: $billingParams)
                    .frame(width: 0, height: 0).environmentObject(NavView.shared)
            }.alert(isPresented: $showAlert, content: {
                Alert(title: Text("Card/Adress Invalid"), message: Text("Please try again."), dismissButton: .default(Text("OK!")))
            }).navigationTitle("Add card")
        }
    }
    
    

    【讨论】:

      【解决方案2】:

      我对 SwiftUI 不够精通,无法帮助您处理现有代码,但 the Stripe iOS SDK was updated to support SwiftUI in version 21.3.0,在 the latest release (21.4.0 at time of writing) 中修复了 SwiftUI 错误,现在包括 some SwiftUI sample code

      也许更新到支持 SwiftUI 的 SDK 版本并重构代码以利用该支持是最好的前进道路?

      【讨论】:

      • 我想出了一个办法。 UI 示例对我并没有真正的帮助,它现在只是一个测试支付流程。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2018-11-14
      • 1970-01-01
      • 2016-07-10
      • 2021-10-11
      • 2015-11-25
      • 1970-01-01
      • 2021-10-12
      相关资源
      最近更新 更多