Stripe with SwiftUI - ios

I'm trying to implement Stripe in my SwiftUI app using the STPApplePayContextDelegate.
I have created a class that conforms to this delegate according to this documentation but no luck. I get this error: // Type of expression is ambiguous without more context in this line let applePayContext = STPApplePayContext(paymentRequest: paymentRequest, delegate: self)
What am I doing wrong here?
struct PaymentButtonController : UIViewControllerRepresentable {
class Coordinator : NSObject, STPApplePayContextDelegate {
var vc : UIViewController?
#objc func buttonPressed() {
let merchantIdentifier = "merchant.com.your_app_name"
let paymentRequest = StripeAPI.paymentRequest(withMerchantIdentifier: merchantIdentifier, country: "US", currency: "USD")
// Configure the line items on the payment request
paymentRequest.paymentSummaryItems = [
// The final line should represent your company;
// it'll be prepended with the word "Pay" (i.e. "Pay iHats, Inc $50")
PKPaymentSummaryItem(label: "iHats, Inc", amount: 50.00),
]
// Initialize an STPApplePayContext instance
if let applePayContext = STPApplePayContext(paymentRequest: paymentRequest, delegate: self) {
// Present Apple Pay payment sheet
if let vc = vc {
applePayContext.presentApplePay(on: vc)
}
} else {
// There is a problem with your Apple Pay configuration
}
}
func applePayContext(_ context: STPApplePayContext, didCreatePaymentMethod paymentMethod: STPPaymentMethod, paymentInformation: PKPayment, completion: #escaping STPIntentClientSecretCompletionBlock) {
let clientSecret = "..."
print("ENDLICH")
// Retrieve the PaymentIntent client secret from your backend (see Server-side step above)
// Call the completion block with the client secret or an error
completion(clientSecret, nil);
}
func applePayContext(_ context: STPApplePayContext, didCompleteWith status: STPPaymentStatus, error: Error?) {
print("ENDLICH")
switch status {
case .success:
// Payment succeeded, show a receipt view
break
case .error:
// Payment failed, show the error
break
case .userCancellation:
// User cancelled the payment
break
#unknown default:
fatalError()
}
}
}
func makeCoordinator() -> Coordinator {
return Coordinator()
}
func makeUIViewController(context: Context) -> UIViewController {
let button = PKPaymentButton(paymentButtonType: .plain, paymentButtonStyle: .automatic)
button.addTarget(context.coordinator, action: #selector(context.coordinator.buttonPressed), for: .touchUpInside)
let vc = UIViewController()
context.coordinator.vc = vc
vc.view.addSubview(button)
return vc
}
func updateUIViewController(_ uiViewController: UIViewControllerType, context: Context) {
}
}

The function you're calling (presentApplePay) expects a UIViewController as its input, but you're passing self, which is your ApplePayContext, defined above as NSObject, ObservableObject, STPApplePayContextDelegate.
The challenge that you're going to face is getting a UIViewController context to pass to it, as you won't have any references to a UIViewController in pure SwiftUI.
You have a few possible solutions:
In your SceneDelegate, pass a reference of your UIHostingController down to your views, then use it as the argument to presentApplePay
Use UIViewControllerRepresentable to get a UIViewController you can embed into your SwiftUI view and past as the argument (https://developer.apple.com/documentation/swiftui/uiviewcontrollerrepresentable)
Use a library like SwiftUI-Introspect to get the underlying UIViewController to your current SwiftUI views (https://github.com/siteline/SwiftUI-Introspect)
Update:
In response your request for code, here's something to start with. Note that not everything is hooked up yet -- you'll need to connect the buttonPressed method, add layout constraints to the button, etc, but it gives you a way to figure out how to get a reference to a UIViewController
struct PaymentButtonController : UIViewControllerRepresentable {
class Coordinator : NSObject {
var vc : UIViewController?
#objc func buttonPressed() {
print("Button with VC: \(vc)")
}
}
func makeCoordinator() -> Coordinator {
return Coordinator()
}
func makeUIViewController(context: Context) -> UIViewController {
let button = PKPaymentButton()
button.addTarget(context.coordinator, action: #selector(context.coordinator.buttonPressed), for: .touchUpInside)
let vc = UIViewController()
context.coordinator.vc = vc
vc.view.addSubview(button)
return vc
}
func updateUIViewController(_ uiViewController: UIViewControllerType, context: Context) {
}
}
Embed in your view by using this in your SwiftUI code:
PaymentButtonController()

Related

Callback is not working when button tapped

I want to trigger an action on button tap with my callback. Also I have presenter and coordinator. But nothing happenes. My code is not working in this closure:
startViewController.output = { [weak self] action in
switch action {
case .registrationButtonTapped:
self?.showRegistrationViewController()
case .loginButtonTapped:
self?.showLoginViewController()
}
}
In my ViewController I have enum:
enum StartViewControllerButton {
case registrationButtonTapped
case loginButtonTapped
}
callback:
var output: ((StartViewControllerButton) -> Void)?
and selectors:
#objc func registrationButtonPressed() {
startModulPresenter.openNextScreen()
self.output?(.registrationButtonTapped)
}
#objc func loginButtonPressed() {
startModulPresenter.openNextScreen()
self.output?(.loginButtonTapped)
}
My Presenter
class StartModulPresenter: StartModulPresenterProtocol {
var navigationController: UINavigationController
var coordinator: CoordinatorProtocol?
//Init
init(navigationController: UINavigationController) {
self.navigationController = navigationController
coordinator = AuthorizationCoordinator(navigationController: navigationController)
}
//Functions
func openNextScreen() {
coordinator?.start()
}
}
My Coordinator:
class AuthorizationCoordinator: RegistrationCoordinatorProtocol {
var presenter: PresenterProtocol?
var navigationController: UINavigationController
var childCoordinators: [CoordinatorProtocol] = []
//Init
init(navigationController: UINavigationController) {
self.navigationController = navigationController
}
func start() {
presenter = StartModulPresenter(navigationController: navigationController)
let startViewController = StartViewController(startModulPresenter: presenter as! StartModulPresenter)
startViewController.output = { [weak self] action in
switch action {
case .registrationButtonTapped:
self?.showRegistrationViewController()
case .loginButtonTapped:
self?.showLoginViewController()
}
}
}
private func showRegistrationViewController() {
let registrationViewController = RegistrationViewController()
registrationViewController.view.backgroundColor = .orange
self.navigationController.pushViewController(registrationViewController, animated: true)
}
private func showLoginViewController() {
let loginViewController = LoginViewController()
loginViewController.view.backgroundColor = .orange
self.navigationController.pushViewController(loginViewController, animated: true)
}
}
Could you check if startViewController is pushed/presented or not?
func start() {
presenter = StartModulPresenter(navigationController: navigationController)
let startViewController = StartViewController(startModulPresenter: presenter as! StartModulPresenter)
startViewController.output = { [weak self] action in
switch action {
case .registrationButtonTapped:
self?.showRegistrationViewController()
case .loginButtonTapped:
self?.showLoginViewController()
}
}
}
And, is self.output is nil or not? If it is nil please check your assignment call, it needed to be called before you use this variable.
#objc func loginButtonPressed() {
startModulPresenter.openNextScreen()
self.output?(.loginButtonTapped)
}
Honestly, I don't recommend you to use this design pattern, just a simple thing but the real result is too complicated.
Just use protocol-based MVC. View communicate with Controller via protocol/closure or Reactive-based with Combine (PassthroughSubject/CurrentValueSubject)

How to integrate Razorpay in SwiftUI without storyboard

I was looking at integrating the Razorpay checkout feature with iOS in Xcode and found the official documentation at https://razorpay.com/docs/payment-gateway/ios-integration/standard/. The documentation helps with integrating the Razorpay with UIViewController. The iOS app I am building does not make use of the storyboard and is strictly SwiftUI. I have looked at multiple ways of incorporating the UIViewController in SwiftUI which is totally possible with UIViewRepresentable but the code structure uses
struct ComponentName: UIViewRepresentable{}
But Razorpay SDK for iOS wants to implement RazorpayPaymentCompletionProtocol to a class and not struct. How do I go about in using this in a strictly SwiftUI application?
You can use coordinators to manage the view controllers, and that coordinator will RazorpayPaymentCompletionProtocol.
Example:
struct ComponentName: UIViewControllerRepresentable {
func makeUIViewController(context: Context) -> CheckoutViewController {
.init()
}
func updateUIViewController(_ uiViewController: CheckoutViewController, context: Context) { }
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
class Coordinator: NSObject, RazorpayPaymentCompletionProtocol {
let parent: ComponentName
typealias Razorpay = RazorpayCheckout
var razorpay: RazorpayCheckout!
init(_ parent: ComponentName) {
self.parent = parent
RazorpayCheckout.initWithKey(razorpayTestKey, andDelegate: self)
}
func onPaymentError(_ code: Int32, description str: String) {
print("error: ", code, str)
// self.presentAlert(withTitle: "Alert", message: str)
// parent.alert with message
}
func onPaymentSuccess(_ payment_id: String) {
print("success: ", payment_id)
// self.presentAlert(withTitle: "Success", message: "Payment Succeeded")
}
}
}
class CheckoutViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
// self.showPaymentForm()
}
}

Swift: Can't dismiss view controller

I have a SwiftUI project in which I'm presenting a view controller to display an advertisement via MoPub.
Everything works as expected except one thing: when I tap the ad's close button, the ad itself closes but the black screen behind the ad continues to show. I guess the view controller is not being dismissed (but the completion block for dismiss does run).
Here's my code:
class InterstitialAds: UIViewController, MPInterstitialAdControllerDelegate {
var moPubView: MPInterstitialAdController?
func viewControllerForPresentingModalView() -> UIViewController! {
return self
}
//Called when you tap the ad's close button:
func interstitialWillDismiss(_ interstitial: MPInterstitialAdController) {
dismissControllerWithoutReward()
}
func showAd() {
let topViewController = UIApplication.shared.windows.filter {$0.isKeyWindow}.first?.rootViewController
self.modalPresentationStyle = .fullScreen
topViewController?.present(self, animated: true) {}
}
func dismissControllerWithoutReward() {
self.dismiss(animated: true) {
print("dismissControllerWithoutReward()") //Successfully prints to console
}
}
override func viewDidLoad() {
let adId = "4f117153f5c24fa6a3a92b818a5eb630" //Test ad unit
self.moPubView = MPInterstitialAdController(forAdUnitId: adId)
if let v = self.moPubView {
v.delegate = self
v.loadAd()
DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) {
v.show(from: self)
}
}
super.viewDidLoad()
}
}
Question:
Why isn't the view controller being dismissed, despite the successful call to dismiss?
Thank you!
Edit:
Interestingly, if I wait 0.5 seconds before trying to dismiss the view controller, it dismisses as desired. So, now I've got this code in interstitialWillDismiss(_:) (but I still want to know why this is happening):
func interstitialWillDismiss(_ interstitial: MPInterstitialAdController) {
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
self.dismissControllerWithoutReward()
}
}
For getting clear implementation and expected behavior you should wrap InterstitialAds controller in UIViewControllerRepresentable, then connect you SwiftUI side with some InterstitialAdsView which implements from UIViewControllerRepresentable via some isPresented flag binding.
Short example:
class InterstitialAds: UIViewController {
let onFlowCompleted: () -> Void
init(onFlowCompleted: #escaping () -> Void) {
self.onFlowCompleted = onFlowCompleted
// ...
}
override func dismiss(animated flag: Bool, completion: (() -> Void)? = nil) {
onFlowCompleted()
// or somewhere else ...
}
}
struct HomeView: View {
#State var shouldShowInterstitialView = false
var body: some View {
Button {
shouldShowInterstitialView = true
} label: {
Text("Show Ad")
}
.fullScreenCover(isPresented: $shouldShowInterstitialView) {
InterstitialAdsView {
shouldShowInterstitialView = false
}
}
}
}
struct InterstitialAdsView: UIViewControllerRepresentable {
// #Environment(\.presentationMode) var presentationMode
// or
// #Binding var isPresented: Bool
// or
let onFlowCompleted: () -> Void
func makeUIViewController(context: Context) -> InterstitialAds {
InterstitialAds(onFlowCompleted: onFlowCompleted)
}
func updateUIViewController(_ uiViewController: InterstitialAds, context: Context) {
// update if needed
}
}

Manage Delegate out UIViewController class

I would like to understand what would be the best way to implement a delegate out UIViewController class
How can I manage the delegate using controller: UIViewController parameter of my function in AuthManager?
These are the two classes I'm working with .. I show you small examples to make you understand
class StartController: UIViewController {
#objc private func presentAuthFacebookController() {
AuthManager.signInWithFacebook(controller: self)
}
}
class AuthManager {
static func signInWithFacebook(controller: UIViewController) {
let loginManager = LoginManager()
loginManager.logIn(permissions: [.publicProfile, .email], viewController: controller) { (result) in
switch result {
case .cancelled : print("\n AuthFacebook: operazione annullata dall'utente \n")
case .failed(let error) : print("\n AuthFacebook: \(error) \n")
case .success(granted: _, declined: let declinedPermission, token: _):
let authVC = ExistingEmailController()
authVC.delegate = // ?????? (controller)
UIApplication.shared.windows.first?.rootViewController?.present(authVC, animated: true, completion: nil)
}
}
}
}
I personally don't think StartController should know about/conform to ExistingEmailControllerDelegate. But if you really want, you can declare controller as a composition type:
static func signInWithFacebook(controller: UIViewController & ExistingEmailControllerDelegate) {
...
authVC.delegate = controller
In my opinion, the whole point of having a AuthManager is to create a layer of abstraction on top of ExistingEmailController, and to encapsulate the logic of authentication. Therefore, StartController shouldn't know, or care, about ExistingEmailControllerDelegate. It only knows about AuthManager.
AuthManager should be the delegate of ExistingEmailController, which implies that signInWithFacebook should not be static, and AuthManager can have an AuthManagerDelegate that StartController conforms to:
class AuthManager : ExistingEmailControllerDelegate {
weak var delegate: AuthManagerDelegate?
func signInWithFacebook(controller: UIViewController) {
...
let authVC = ExistingEmailController()
authVC.delegate = self
UIApplication.shared.windows.first?.rootViewController?.present(authVC, animated: true, completion: nil)
}
func someMethodFromExistingEmailControllerDelegate() {
delegate?.someMethod() // delegating it self.delegate, which StartController conforms to
}
}
protocol AuthManagerDelegate : class {
func someMethod()
}
class StartController: UIViewController, AuthManagerDelegate {
var authManager: AuthManager!
override func viewDidLoad() {
authManager = AuthManager()
authManager.delegate = self
}
#objc private func presentAuthFacebookController() {
authManager.signInWithFacebook(controller: self)
}
func someMethod() {
// write here the code that you would have written in someMethodFromExistingEmailControllerDelegate
}
}

FirebaseUI and SwiftUI LoginG

I´m trying to implement the firebaseUI login in an iOS app using swiftUI.
I´m able to use the main login view, but i can not control the flow after the user was loged on.
This is the code of my "login controller"
import SwiftUI
import FirebaseUI
struct CustomLogin: UIViewControllerRepresentable {
func makeUIViewController(context: Context) -> UIViewController {
let authUI = FUIAuth.defaultAuthUI()
let providers: [FUIAuthProvider] = [
FUIEmailAuth(),
FUIFacebookAuth()
// FUIPhoneAuth(authUI:FUIAuth.defaultAuthUI()),
]
authUI?.providers = providers
let authViewController = authUI?.authViewController()
// let controller = UIViewController()
// authUI!.delegate = self
return authViewController!
}
func updateUIViewController(_ uiViewController: UIViewController, context: Context) {
}
}
struct CustomLogin_Previews: PreviewProvider {
static var previews: some View {
CustomLogin()
}
}
I can show the login flow by add CustomLogin())to my content view.
how can I manage the call backs after the user has logged on?
The user was created in firebase, but the view don´t update.
if someone has implemented a firebaseUI login with SwiftUI I appreciated it.
Thanks
I was able to implement something that worked based off of your code and some Apple tutorials. Basically, you need to add a coordinator to your UIViewControllerRepresentable. You can add your delegate to the coordinator.
Here's my scratch code that works:
import SwiftUI
import FirebaseUI
import Firebase
public var screenWidth: CGFloat {
return UIScreen.main.bounds.width
}
public var screenHeight: CGFloat {
return UIScreen.main.bounds.height
}
struct LoginView : View {
#State private var viewState = CGSize(width: 0, height: screenHeight)
#State private var MainviewState = CGSize.zero
var body : some View {
ZStack {
CustomLoginViewController { (error) in
if error == nil {
self.status()
}
}.offset(y: self.MainviewState.height).animation(.spring())
MainView().environmentObject(DataStore()).offset(y: self.viewState.height).animation(.spring())
}
}
func status() {
self.viewState = CGSize(width: 0, height: 0)
self.MainviewState = CGSize(width: 0, height: screenHeight)
}
}
struct LoginView_Previews : PreviewProvider {
static var previews : some View {
LoginView()
}
}
struct CustomLoginViewController : UIViewControllerRepresentable {
var dismiss : (_ error : Error? ) -> Void
func makeCoordinator() -> CustomLoginViewController.Coordinator {
Coordinator(self)
}
func makeUIViewController(context: Context) -> UIViewController
{
let authUI = FUIAuth.defaultAuthUI()
let providers : [FUIAuthProvider] = [
FUIEmailAuth(),
FUIGoogleAuth(),
FUIOAuth.appleAuthProvider()
]
authUI?.providers = providers
authUI?.delegate = context.coordinator
let authViewController = authUI?.authViewController()
return authViewController!
}
func updateUIViewController(_ uiViewController: UIViewController, context: UIViewControllerRepresentableContext<CustomLoginViewController>)
{
}
//coordinator
class Coordinator : NSObject, FUIAuthDelegate {
var parent : CustomLoginViewController
init(_ customLoginViewController : CustomLoginViewController) {
self.parent = customLoginViewController
}
// MARK: FUIAuthDelegate
func authUI(_ authUI: FUIAuth, didSignInWith authDataResult: AuthDataResult?, error: Error?)
{
if let error = error {
parent.dismiss(error)
}
else {
parent.dismiss(nil)
}
}
func authUI(_ authUI: FUIAuth, didFinish operation: FUIAccountSettingsOperationType, error: Error?)
{
}
}
}

Resources