Flutter iOS close SFSafariViewController after stripe payment is complete - flutter

I've integrated stipe payment into a Flutter (using this plugin https://pub.dev/packages/stripe_payment) app and it all works fine except for the fact that stripe's 3d secure safari view controller is never closed after the payment is complete so the users must always close it by hand which is not too good
In my AppDelegate.swift I've implemented an application method override like so
override func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool {
if (url.absoluteString.contains("stripe")) {
/// this code works and I see in in console
print("STRIPE CALLBACK WORKED");
return true;
}
return false
}
I've also added a deep link to be called by safari when the job is done
var paymentIntentResult = await StripePayment.confirmPaymentIntent(
PaymentIntent(
clientSecret: clientSecret,
paymentMethodId: paymentMethodId,
returnURL: 'mycoolapp://stripe-redirect',
),
);
and indeed the link is called, I can clearly see "STRIPE CALLBACK WORKED" output in xcode's console.
So the question is why doesn't the sfsafariviewcontroller close and the process does not return to the Flutter app by itself?
Instead I see this window
I even tried to remove it by hand using different hacks, like this for example
func popAfterDelay(view uiViewController:UIViewController?) {
print("POP AFTER DELAY CALLED")
DispatchQueue.main.asyncAfter(deadline: .now() + 2.0) {
if (uiViewController is SFSafariViewController) {
print("DISMISS SAFARY VIEW..........")
let safariController = uiViewController as? SFSafariViewController;
safariController?.removeFromParent()
}
}
}
override func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool {
if (url.absoluteString.contains("stripe")) {
let curView = ViewUtils.getCurrentViewController();
if (curView != nil) {
popAfterDelay(view: curView)
}
return true;
}
return false
}
I could see "DISMISS SAFARY VIEW.........." output but to no avail.
I'm completely stuck with this. Any help would be highly appreciated

Related

Flutter IOS Universal Link Opens App, But Does Not Navigate To Correct Page

I have set up Universal Links on my flutter project for IOS.
Like the title suggests, my app does open when I click on a link relating to my site but it does not navigate to the correct page. It just opens the app.
I'm not using the uni_links package, rather I used a combination of guides (including official documentation):
https://developer.apple.com/videos/play/wwdc2019/717/
https://nishbhasin.medium.com/apple-universal-link-setup-in-ios-131a508b45d1
https://www.kodeco.com/6080-universal-links-make-the-connection
I have setup my apple-app-site-association file to look like:
{
"applinks": {
"details": [
{
"appIDs": [
"XXXXXXX.com.my.appBundle"
],
"componenents": [
{
"/": "/*"
}
]
}
]
}
}
and I have added this to my info.plist file:
<key>FlutterDeepLinkingEnabled</key>
<true/>
and my AppDelegate.swift file looks like:
import UIKit
import Flutter
import Firebase
#UIApplicationMain
#objc class AppDelegate: FlutterAppDelegate {
override func application(_ application: UIApplication, continue userActivity: NSUserActivity,
restorationHandler: #escaping ([UIUserActivityRestoring]?) -> Void) -> Bool {
// This will allow us to check if we are coming from a universal link
// and get the url with its components
// The activity type (NSUserActivityTypeBrowsingWeb) is used
// when continuing from a web browsing session to either
// a web browser or a native app. Only activities of this
// type can be continued from a web browser to a native app.
guard userActivity.activityType == NSUserActivityTypeBrowsingWeb,
let url = userActivity.webpageURL,
let components = URLComponents(url: url, resolvingAgainstBaseURL: true) else {
return false
}
// Now that we have the url and its components,
// we can use this information to present
// appropriate content in the app
return true
}
override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
FirebaseApp.configure()
GeneratedPluginRegistrant.register(with: self)
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
}
My Runner-entitlements are also setup correctly like:
<key>com.apple.developer.associated-domains</key>
<array>
<string>applinks:www.example.com</string>
<string>applinks:*.example.com</string>
</array>
The issue is, if I click a hyperlink for www.example.com/mypath , it does not got to the page/route handled by /mypath, but instead just opens the app.
My routing is done using go_router: ^5.2.4
Please does anyone know why this is happening? I'm blocked by this. I have seen similar questions, but none with answers that have worked for me. Any help is appreciated.
Ok so figured it out. The official apple documentation requests the addition of a variation of this function in the AppDelegate.swift file:
func application(_ application: UIApplication, continue userActivity: NSUserActivity,
restorationHandler: #escaping ([UIUserActivityRestoring]?) -> Void) -> Bool {
// This will allow us to check if we are coming from a universal link
// and get the url with its components
// The activity type (NSUserActivityTypeBrowsingWeb) is used
// when continuing from a web browsing session to either
// a web browser or a native app. Only activities of this
// type can be continued from a web browser to a native app.
guard userActivity.activityType == NSUserActivityTypeBrowsingWeb,
let url = userActivity.webpageURL,
let components = URLComponents(url: url, resolvingAgainstBaseURL: true) else {
return false
}
// Now that we have the url and its components,
// we can use this information to present
// appropriate content in the app
return true
}
Seems that it conflicts with the flutter framework for handling universal links. Taking that function out and just having this in my info.plist worked (everything else stayed the same):
<key>FlutterDeepLinkingEnabled</key>
<true/>
Flutter documentation is not out for this (as at the time of posting this answer) so if people are interested, I could do a small article on the necessary steps.
When you handle the dynamic link you get the universal link and other data in the userActivity parameter of the following function.
func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: #escaping ([UIUserActivityRestoring]?) -> Void) -> Bool {
if let incomingURL = userActivity.webpageURL {
debugPrint("incoming url is", incomingURL)
let link = DynamicLinks.dynamicLinks().shouldHandleDynamicLink(fromCustomSchemeURL: incomingURL)
print(link)
let linkHandle = DynamicLinks.dynamicLinks().handleUniversalLink(incomingURL) { link, error in
guard error == nil else {
print("Error found.")
return
}
if let dynamicLink = link {
self.handleDynamicLinks(dynamicLink)
}
}
if linkHandle {
return true
} else {
return false
}
}
return false
}
Parse the data from another function or you can parse in above code also. In my case I parsed the code in below function.
func handleDynamicLinks(_ dynamicLink: DynamicLink) {
guard let link = dynamicLink.url else {
return
}
if let landingVC = self.window?.rootViewController as? LandingViewController {
// Do you your handling here with any controller you want to send or anything.
}
// example you are getting ID, you can parse it here
if let idString = link.valueOf("id"), let id = Int.init(idString) {
print(id)
}
}
When you get the details from the link you can simply fetch the navigation controller or the VisibleController, and then can push to the desired flow.

400 Invalid_request You can't sign in to this app because it doesn't comply with Google's OAuth 2.0 policy for keeping apps secure

I am getting this error when I am trying to register with Google in my IOS app. I have got the REVERSED-CLIEND_ID which looks like something like this: com.googleusercontent.apps..... So far I can open the google window and I get the 400 error.
AppDelegate:
class AppDelegate: UIResponder, UIApplicationDelegate {
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
ApplicationDelegate.shared.application(application,didFinishLaunchingWithOptions: launchOptions)
GIDSignIn.sharedInstance.restorePreviousSignIn { user, error in
if error != nil || user == nil {
// Show the app's signed-out state.
} else {
// Show the app's signed-in state.
}
}
return true
}
func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool {
ApplicationDelegate.shared.application(
app,
open: url,
sourceApplication: options[UIApplication.OpenURLOptionsKey.sourceApplication] as? String,
annotation: options[UIApplication.OpenURLOptionsKey.annotation]
)
var handled: Bool
handled = GIDSignIn.sharedInstance.handle(url)
if handled {
return true
}
// Handle other custom URL types.
// If not handled by this app, return false.
return false
}
}
ViewController:
let signInConfig = GIDConfiguration.init(clientID: "REVERSED_URL_THING")
#IBAction func googleRegister(_ sender: UIButton) {
GIDSignIn.sharedInstance.signIn(
with: signInConfig,
presenting: self
) { user, error in
guard error == nil else { return }
guard let user = user else { return }
// Your user is signed in!
}
}
If anyone came across the same issue, in Xcode's URL Types add iOS URL scheme and do not use reversed url like it was said in many tutorials. Use the CLIEND ID which Google provides:
let signInConfig = GIDConfiguration.init(clientID: "CLIENT ID ")

Support URL schemes in macOS application

There are several (old) questions on this subject, but none of the solutions worked for me, so here's the question:
How to add a URL scheme, so it will be possible to open my app via the browser?
I did the following:
Added the required info to the info.plist:
I Added those functions:
func applicationWillFinishLaunching(_ notification: Notification) {
NSAppleEventManager.shared().setEventHandler(self, andSelector: #selector(handleEvent(_:with:)), forEventClass: AEEventClass(kInternetEventClass), andEventID: AEEventID(kAEGetURL))
}
#objc func handleEvent(_ event: NSAppleEventDescriptor, with replyEvent: NSAppleEventDescriptor) {
NSLog("at handleEvent")
}
I also tried to add this function:
func application(_ application: NSApplication, open urls: [URL]) {
for url in urls {
NSLog("url:\(url)")
}
}
None of the above worked. I have a webpage that redirect with MyAppLogin://test but nothing happens. It doesn't matter if the app is open or closed (I want it to work in both cases)
Any idea what's the problem here?
Edit: Two more details:
The app is sandboxed
I'm running it via Xcode (so the installation is not at the 'Applications' folder)
One year later, this is how I made it work:
Add this to your AppDelegate:
func applicationWillFinishLaunching(_ notification: Notification) {
let appleEventManager: NSAppleEventManager = NSAppleEventManager.shared()
appleEventManager.setEventHandler(self, andSelector: #selector(handleGetURLEvent(_:withReplyEvent:)), forEventClass: AEEventClass(kInternetEventClass), andEventID: AEEventID(kAEGetURL))
}
Add this, to parse the URL and do whatever you want:
#objc func handleGetURLEvent(_ event: NSAppleEventDescriptor, withReplyEvent: NSAppleEventDescriptor) {
if let urlString = event.forKeyword(AEKeyword(keyDirectObject))?.stringValue {
let url = URL(string: urlString)
guard url != nil, let scheme = url!.scheme else {
//some error
return
}
if scheme.caseInsensitiveCompare("yourSchemeUrl") == .orderedSame {
//your code
}
}
}

MPRemoteCommandCenter not available after changing audio session category

My app has got the option to allow its sound to be mixed with other apps.
According to Apple, MPRemoteCommandCenter is only available when apps do not allow for mixing.
Inside my app, when the user taps the button to change the mixWithOthers setting, the audio session is set accordingly.
However, even when the user switches back to not allow mixing anymore MPRemoteCommandCenter will not show up in lock screen until the app has been removed from cache (swiped up) and started again.
Is there a way to achieve the desired behaviour without having to re-start the app?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
UIApplication.shared.beginReceivingRemoteControlEvents()
}
var isMixWithOthersAllowed: Bool
func startProcess() {
setAudioSession {
setupMediaControls()
}
}
func setAudioSession(completion: #escaping () -> Void) {
let audioSession = AVAudioSession.sharedInstance()
do {
if isMixWithOthersAllowed {
try audioSession.setCategory(.playback, options: [.mixWithOthers])
/* The remote command center will not be available when mixing with others,
as stated by Apple in the docs. */
} else {
try audioSession.setCategory(.playback)
/* The remote command center should be available when switching back to
this category, but it will only show up after the app has been killed
and started fresh again. I'd like it to be available without restarting
the application. */
}
try audioSession.setActive(true)
} catch let error as NSError {
configureAudioSessionError = error
}
assert(configureAudioSessionError == nil, "Create audio session failed")
completion()
}
func setupMediaControls() {
let commandCenter = MPRemoteCommandCenter.shared()
commandCenter.playCommand.isEnabled = true
commandCenter.pauseCommand.isEnabled = true
commandCenter.nextTrackCommand.isEnabled = true
commandCenter.previousTrackCommand.isEnabled = true
// Setup handlers for commands
// ...
setupNowPlaying()
}
func setupNowPlaying() {
// Configure audio track metadata and update UI elements
}

snapkit login kit not working Swift Xcode 11.3.1

I am using Xcode 11.3.1 and try to login with snapchat with loginkit I add the add information in info.plist and my code is
SCSDKLoginClient.login(from: self, completion: { success, error in
if let error = error {
print(error.localizedDescription)
return
}
if success {
self.fetchSnapUserInfo() //example code
}
})
this code show me the login ui of snapchat and I am login into snapchat with my account.
but I am stuck on this ui
when I am click on continue nothing is happing . SCSDKLoginClient completion block not called.
Please ensure your URL scheme is configured correctly.
URL Schemes
hello everyOne soo finally i found the solution
i am using 11.3.1 and when i create new project the add
AppDelegate and SceneDelegate default class.
so according to snapchat logkit documentation i add
func application(_ app: UIApplication,
open url: URL,
options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool {
return SCSDKLoginClient.application(app, open: url, options: options)
}
method in my Appdelegate class. but this method never get called in xocode 11.3.1
so the solution of my problem is this
func scene(_ scene: UIScene, openURLContexts URLContexts: Set<UIOpenURLContext>) {
guard let url = URLContexts.first?.url else {
return
}
SCSDKLoginClient.application( UIApplication.shared, open: url, options: nil)
}
you need to add this method into your sceneDelegate file. then everything work fine.
synpchat need to update there doc for new xcode 11.3.1. i hope this answer help you guy's because i also wast my 3 day's on this issue.
happy Coding :)