Closed Pic2490 closed 3 years ago
Hello, thanks for filing this! We're still working on some better documentation for our SwiftUI support. For now, here's an example app showing how to build a custom integration with SwiftUI: https://github.com/stripe-samples/accept-a-payment/tree/main/custom-payment-flow/client/ios-swiftui
We don't have an example for SetupIntents at the moment, but the flow is similar to the PaymentIntent example. As shown in https://github.com/stripe-samples/accept-a-payment/blob/main/custom-payment-flow/client/ios-swiftui/AcceptAPayment/Views/Card.swift, you'll want to use a STPPaymentCardTextField.Representable(paymentMethodParams: $paymentMethodParams)
SwiftUI view, with a binding to your STPPaymentMethodParams and an isConfirmingSetupIntent
Bool. You'll then want to use the .setupIntentConfirmationSheet()
ViewModifier to confirm the SetupIntent:
.setupIntentConfirmationSheet(isConfirmingSetupIntent: $isConfirmingSetupIntent,
setupIntentParams: setupIntent,
onCompletion: model.onCompletion)
Your Button action can then set isConfirmingSetupIntent
to true
, which will trigger the SetupIntent confirmation.
Let me know if you'd like additional clarification on any of this!
Hi, I've tried your suggestions but I now have an error with STPPaymentHandlerActionStatus
failing
SwiftView (Note: assume card params and client secret are valid)
Button(action:{
//Make sure card is valid before saves
print("isValid: \(self.isValid)")
if(self.isValid){
//Set Stripe Values
self.cardField.number = self.cardNumber
self.cardField.expMonth = NumberFormatter()
.number(from: self.cardMonth )
self.cardField.expYear = NumberFormatter()
.number(from: self.cardYear )
self.cardField.cvc = self.cardCvv
print("ccNumber: \(self.cardField.number ?? "Error")")
let billingDetails = STPPaymentMethodBillingDetails()
let paymentMethodParams =
STPPaymentMethodParams(card: self.cardField,
billingDetails: billingDetails,
metadata: nil)
print("Testing: \(self.clientSecret)")
self.setupIntentParams = STPSetupIntentConfirmParams(clientSecret: self.clientSecret)
self.setupIntentParams.paymentMethodParams = paymentMethodParams
self.showSetupIntent = true
print("paymentStatus: \(self.model.paymentStatus ?? .failed)")
}
}){
ConfirmButton(text: "Save")
}
.setupIntentConfirmationSheet(isConfirmingSetupIntent: self.$showSetupIntent,
setupIntentParams: self.setupIntentParams,
onCompletion: model.onCompletion)
BackendModel
class BackendModel : ObservableObject {
@Published var paymentStatus: STPPaymentHandlerActionStatus?
@Published var paymentIntentParams: STPPaymentIntentParams?
@Published var lastPaymentError: NSError?
var paymentMethodType: String?
var currency: String?
func onCompletion(status: STPPaymentHandlerActionStatus, pi: STPSetupIntent?, error: NSError?) {
self.paymentStatus = status
self.lastPaymentError = error
// MARK: Demo cleanup
if status == .succeeded {
print("Status: Success")
// A PaymentIntent can't be reused after a successful payment. Prepare a new one for the demo.
self.paymentIntentParams = nil
}
if status == .failed{
print("Status: Failed")
}
}
}
When I hit save on my swift view, BackendModel prints("Status: Failed") What should I do from here? Thanks
As of this writing this solution worked for me:
import SwiftUI
import Stripe
import UIKit
struct StripeBuyView: View {
@EnvironmentObject var viewModel: ViewModel
@State var isPressed = false
var body: some View {
VStack {
Button(action: {
isPressed = !isPressed
}, label: {
Text("Buy")
})
FakeStripeView(isPressed: $isPressed)
.frame(width: 0, height: 0)
}
}
}
struct FakeStripeView: UIViewControllerRepresentable {
@EnvironmentObject var viewModel: ViewModel
@Binding var isPressed: Bool
public typealias UIViewControllerType = FakeStripeViewController
func makeUIViewController(context: UIViewControllerRepresentableContext<FakeStripeView>) -> FakeStripeViewController {
let viewController = FakeStripeViewController(viewModel: viewModel)
return viewController
}
func updateUIViewController(_ uiViewController: FakeStripeViewController, context _: UIViewControllerRepresentableContext<FakeStripeView>) {
if isPressed {
uiViewController.sendBuyOrder()
}
}
}
class FakeStripeViewController: UIViewController {
var viewModel: ViewModel?
convenience init() {
self.init(viewModel: nil)
}
init(viewModel: ViewModel?) {
self.viewModel = viewModel
super.init(nibName: nil, bundle: nil)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
func sendBuyOrder() {
// Perform Stripe calls with ViewModel data
}
}
extension FakeStripeViewController: STPAuthenticationContext {
func authenticationPresentingViewController() -> UIViewController {
self
}
}
@Pic2490 Is this still the solution you are using?
The stripe setup future payment method tutorials don't really address the simple connect use-case of saving a customer's card using SwiftUI custom views and especially handling the STPAuthenticationContext
. They are more geared toward store fronts using prebuilt payment flows which is not a connect use case. We build our own payment flow, this is just about adding a card thats it 😞
@Pic2490 Is this still the solution you are using?
The stripe setup future payment method tutorials don't really address the simple connect use-case of saving a customer's card using SwiftUI custom views and especially handling the
STPAuthenticationContext
. They are more geared toward store fronts using prebuilt payment flows which is not a connect use case. We build our own payment flow, this is just about adding a card thats it 😞
I haven't changed anything since, and it's working fine. If you come across any issues let me know
Is there an example using a swiftui, for the most very base case that is less anti-pattern? Instead of wrapped around delegation methods, is there some sort of singleton i can use to pass stripe whatever it needs from the callback of https://developer.apple.com/documentation/passkit/paywithapplepaybutton
try await StripeApplePay.shared.finished(paymentIntentSecret, whateverYouNeed)
@MainActor
class ViewModel: NSObject, ObservableObject {
var paymentRequest: PKPaymentRequest = {
// truncated
return StripeAPI.paymentRequest(withMerchantIdentifier: config.merchantId, country: "US", currency: "USD")
}()
}
struct MyView: View {
@ObservedObject var model = ViewModel()
var body: some View {
VStack {
PayWithApplePayButton(request: model.paymentRequest) { phase in
// Give stripe whatever it needs
} fallback: {
}
}
}
}
I noticed your team has implemented solutions for when SwiftUI users are integrating your PrebuiltUIs here:
https://stripe.com/docs/payments/accept-a-payment#ios
However, the original poster needed a solution to saving a card with a custom view: https://stripe.com/docs/payments/accept-a-payment?platform=ios&ui=custom
of which I unfortunately can not find any. Out of the 3 tabs, 'Card Element Only' doesn't have a SwiftUI example
I get to the point where:
I have this code inside of a
I was wondering if you can point me towards the right direction or restore the code that was deleted? Thanks!