How can I get the description error from stripe? - swift

I am integrating stripe in my app. The code works but I would like to get the description error and show it to user with a label in case something goes wrong (CC number not correct etc):
// other stuff here
STPAPIClient.sharedClient().createTokenWithCard(stripeCard) { (token, error) -> Void in
if let error = error {
print(error.userInfo)
}
else if let token = token {
self.createBackendChargeWithToken(token) { status in
if status == PKPaymentAuthorizationStatus.Success{
dispatch_async(dispatch_get_main_queue()){
self.paymentActivity.hidden = true
self.paymentActivity.stopAnimating()
self.paymentActivityLabel.text = "Transaction approved!"
}
}
}
}
}
}
when I print error.userInfo I get the following:
[com.stripe.lib:ErrorMessageKey: Your card number is incorrect.,
com.stripe.lib:CardErrorCodeKey: com.stripe.lib:IncorrectNumber,
com.stripe.lib:ErrorParameterKey: number,
NSLocalizedDescription: Your card's number is invalid]
How can I extrapolate NSLocalizedDescription?

An error.userInfo is usually an NSDictionary.
When you print this dictionary, there's no types like with a Swift dictionary, so the print function does not always recognize what are strings, and does not show double quotes. But your userInfo dictionary keys are still probably strings.
I would first try accessing the error value like this:
print(error.userInfo["NSLocalizedDescription"])
to verify that indeed NSLocalizedDescription is a string key.
A tip: hold ALT and CLICK on a variable, it will show you its type, it helps for quickly debugging in Xcode.

Related

Swift Firebase Authentication - two questions about error handling (I'm not sure how to name these errors)

It's really hard to find a proper title for this question. Please be easy on me.
The first part is a check to see if an account exists:
Auth.auth().fetchSignInMethods(forEmail: userEmail, completion: {
(providers, error) in
if error != nil {
self.displayAlertMessage(alertTitle: "Unhandled error", alertMessage: "Undefined error #SignUpViewController_0001");
return;
} else if providers != nil {
self.displayAlertMessage(alertTitle: "Error", alertMessage: "This account is not exist.");
return;
}
})
As you can see, I have something named Unhandled error with message Undefined error. I don't know how to name it properly. Can somebody explain that part to me?
The second one is about getting a localized string - any ideas to make it fancy?
Auth.auth().createUser(withEmail: userEmail, password: userPassword) { user, error in if error == nil && user != nil {
self.displayAlertMessage(alertTitle: "Success", alertMessage: "Your account created successfully. We send you a verification email.", dismiss: true);
} else {
self.displayAlertMessage(alertTitle: "Firebase error", alertMessage: "(error!.localizedDescription)");
}
}
Thanks for tips :)
You can handle the Errors this way:
Auth.auth().fetchSignInMethods(forEmail: email, completion: { (response, error) in
if let error = error, let errCode = AuthErrorCode(rawValue: error._code)
{
switch errCode {
case .emailAlreadyInUse:
GeneralHelper.sharedInstance.displayAlertMessage(titleStr: LocalizeConstant.CommonTitles.Alert.rawValue.localizedStr(), messageStr: LocalizeConstant.CommonTitles.Continue.rawValue.localizedStr())
case .accountExistsWithDifferentCredential:
GeneralHelper.sharedInstance.displayAlertMessage(titleStr: LocalizeConstant.CommonTitles.Alert.rawValue.localizedStr(), messageStr: LocalizeConstant.CommonTitles.Continue.rawValue.localizedStr())
default:
break
}
return
}
}
Here I am getting the errCode using AuthErrorCode provided by Firebase itself and then, I am passing in the received error code using error._code. So, now I can get the type of AuthErrorCode. Using this I am making cases like .emailAlreadyInUser, .accountExistsWithDifferentCredential etc. You can just type . and it will show you all the AuthErrorCodes. So, you can simply handle the error codes in this way.
Now, coming to the second part of the question, i.e. getting localized string. You can add localization to Firebase, for that you have to select the language code. Auth.auth().languageCode = "en" //For English. But, I do not think that it gives localized errors as there are many more languages than what Firebase supports. This mainly for sending localized emails.
To handle the localization, you have to create your own method as I did. You can see that I have called a function displayAlertMessage in which I am passing thetitleStr: LocalizeConstant.CommonTitles.Alert.rawValue.localizedStr(), which is a part of localization.
struct LocalizeConstant {
enum CommonTitles: String
{
case Alert = "common_alert"
}
}
This value designates to the key given by me in the localization file. If you do not know about localization, you have to do a Google search on it. Let's say I have two Localizable.strings one is in English and the other one is in French. In Localizable.strings(English), I've written Alert like this:
"common_alert" = "Alert";
And, In French:
"common_alert" = "Alerte!";
So, this is how I have manually added localization in my app. But, to achieve this you have to do two things. 1) You have to set up your appLanguage. 2) You have to call a method which will fetch the values from these keys defined in the Localizable.strings file.
To do this, I have created a method localizedStr(). It is an extension to String and you can use it as follows.
extension String{
func localizedStr() -> String
{
var finalRes = ""
if let path = Bundle.main.path(forResource: Constants.appLang, ofType: "lproj") //Constants.appLang is "en" here for "English", but you can set accordingly.
{
if let bundle = Bundle(path: path)
{
finalRes = NSLocalizedString(self, tableName: nil, bundle: bundle, value: " ", comment: " ")
}
}
return finalRes
}
}
Now, this method localizedStr() will give you a localized string according to your app language. Even, if Firebase provides localized error codes(which I think it does not), it is impossible to get the error description in each language. So this is the best way I came up with. It may not be the best method out there in the world, but it does the task.
P.S.: To optimize this throughout the app, either you can create an extension to AuthErrorCode or you can create a Helper function where you will just pass the error._code and it will return the localized string. I've added the long way so that you can understand everything in the best way.

Retrieve product localised price - Unexpected non-void return value in void function

I am using SwiftyStoreKit to implement iAP within my project. I have a auto-renewable subscription that users can purchase and restore. This seems to work fine however I am having issues when retrieving the product information to display to the UI.
I am trying to get the localised price to display, the price returned is the yearly cost so I need to divide the number by 12 to also display it as a monthly cost. However when getting the price and trying to return to value I get the following error:
Unexpected non-void return value in void function
Setting the button value
let subscribeButton = subscriptionManager.getSubscriptionPricePerMonth(isYearly: false)
subscribeButton(monthlyCost, for: .normal)
Retrieving the price
//Get prices
func getSubscriptionPricePerMonth() -> String {
let productId = getProductId()
NetworkActivityIndicatorManager.networkOperationStarted()
SwiftyStoreKit.retrieveProductsInfo([productId]) { result in
NetworkActivityIndicatorManager.networkOperationFinished()
if let product = result.retrievedProducts.first {
let priceString = product.localizedPrice!
return priceString
} else if let invalidProductId = result.invalidProductIDs.first {
//return ("Could not retrieve product info", message: "Invalid product identifier: \(invalidProductId)")
print("Could not retrieve product info\(invalidProductId)")
} else {
let errorString = result.error?.localizedDescription ?? "Unknown error. Please contact support"
//return ("Could not retrieve product info, \(errorString)")
print("\(errorString)")
}
}
}
Unexpected non-void return value in void function
Error says, that you are trying to return some value from the void function.
Now, let's understand your case.
Your actual function is
func getSubscriptionPricePerMonth() -> String {}
which you are expecting to return you some value as a String. But looking at the code inside, there you are using asynchronous block, which has void return type
SwiftyStoreKit.retrieveProductsInfo([productId]) { result -> Void in
// Here you are returning some values after parsing the data, which is not allowed.
}
For returning something out of the block, you can use DispatchGroup to make it synchronous
Hope this helps.

Workaround for EKParcipant URL accessing crash?

Some of my users have been sent me logs identifying a EXC_BREAKPOINT (SIGTRAP) Error on this line of code. I've been been trying to make it safe but all of the properties of EKParticipant are non optional so comparing to nil just gives me a warning saying it will always be true. If something is nil here how should I handle it?
Error Line
let participantEmail : String? = participant.url.absoluteString.lowercased().replacingOccurrences(of: "mailto:", with: "")
Apple Error Description
Trace Trap [EXC_BREAKPOINT // SIGTRAP]
Similar to an Abnormal Exit, this exception is intended to give an
attached debugger the chance to interrupt the process at a specific
point in its execution. You can trigger this exception from your own
code using the __builtin_trap() function. If no debugger is attached,
the process is terminated and a crash report is generated. Lower-level
libraries (e.g, libdispatch) will trap the process upon encountering a
fatal error. Additional information about the error can be found in
the Additional Diagnostic Information section of the crash report, or
in the device's console. Swift code will terminate with this exception
type if an unexpected condition is encountered at runtime such as:
a non-optional type with a nil value
a failed forced type conversion Look at the Backtraces to determine where the unexpected condition was encountered. Additional
information may have also been logged to the device's console. You
should modify the code at the crashing location to gracefully handle
the runtime failure. For example, use Optional Binding instead of
force unwrapping an optional."
Full Method
/**
Parses participants for a given event.
Goes through the EKEvents attendees array to build Attendee objects used to model a participant.
- parameter event: The calendar event we'll be finding the participants for.
- returns: An array of Attendee objects with the participants name, email, required/optional status and whether they've accepted their invitation to the event.
*/
private static func parseParticipantsIn(event: EKEvent) -> [Attendee] {
var participants = [Attendee]()
if let attendees = event.attendees, event.attendees?.isEmpty == false {
for participant in attendees {
let participantName : String? = parse(EKParticipantName: participant)
let participantEmail : String? = participant.url.absoluteString.lowercased().replacingOccurrences(of: "mailto:", with: "")
let isRequiredParticipant : Bool = participant.participantRole == EKParticipantRole.required
let hasAccepted : Bool = participant.participantStatus == EKParticipantStatus.accepted
guard (participantName != nil && participantEmail != nil)
else
{
log.error("Participant could not be parsed")
continue
}
let attendee = Attendee(name: participantName!, email: participantEmail!, required: isRequiredParticipant, hasAccepted: hasAccepted)
participants.append(attendee)
}
}
return participants
}
This appears to be a problem with the EKParticipant.url property. Any attempted access of EKParticipant.url causes a crash if you have a participant with the following email field within it.
"Bill Gates" <billgates#google.com>
I'd guess the quotation marks end the String prematurely. It is fine when accessed from EKParticipant.description so I intend to parse it from there.
This is a ridiculous issue, and Deco pinpointed it exactly. I used a different approach to get around it though: Since I'm already working in a mixed code base (obj-c and Swift), I created a class method on one of my obj-c classes that takes an EKParticipant and returns its URL as a string. Then, in Swift, I call that class method to get the URL instead of directly accessing the property (and crashing). It's hacky, but better than crashing and saved me from parsing the description.
This is rather old question but yet I hit this issue myself. My solution is to fallback to ObjC in order to workaround it.
Just add this ObjC functions to swift bridging header file and you are good to use them in swift.
static inline BOOL
participantHasNonNilURL (EKParticipant* _Nonnull participant) {
return participant.URL != nil;
}
static inline NSURL* _Nullable
participantURL(EKParticipant* _Nonnull participant) {
if (participant.URL != nil) {
return participant.URL;
}else {
return nil;
}
}
Example of usage:
extension EKParticipant {
var optionalURL: URL? {
return participantURL(self)
}
var hasURL: Bool {
return participantHasNonNilURL(self)
}
}
This is still an issue on macOS 11.2... I have reported it to Apple. I encourage anyone else hitting this issue to do the same.
The only Swift-only workaround that worked for me is:
extension EKParticipant {
public var safeURL: URL? {
perform(#selector(getter: EKParticipant.url))?.takeUnretainedValue() as? NSURL? as? URL
}
}
Validations are added incorrectly, please check the below response about how the guard could be used.
if let attendees = event.attendees, event.attendees?.isEmpty == false {
for participant in attendees {
guard let participantName : String? = parse(EKParticipantName: participant) else{
log.error("error in participant name")
return
}
guard let participantEmail : String? = participant.url.absoluteString.lowercased().replacingOccurrences(of: "mailto:", with: "") else{
log.error("error in participant email")
return
}
let isRequiredParticipant : Bool = participant.participantRole == EKParticipantRole.required
let hasAccepted : Bool = participant.participantStatus == EKParticipantStatus.accepted
/* guard validation is not required here */
if (participantName != nil && participantEmail != nil){
let attendee = Attendee(name: participantName!, email: participantEmail!, required: isRequiredParticipant, hasAccepted: hasAccepted)
participants.append(attendee)
}
}
}
return participants

In Swift can you trap "fatal error unexpectedly found nil while unwrapping an optional value"?

I'm parsing an NSDictionary. I don't want to go thru item by item to make sure everything is really there and nothing is unexpectedly nil. I figured I'd just do-try-catch. But I'm getting a compiler warning. Here's my code: saying:
do {
try adminMsg = NSDictionary(objects: [rawMsg["msg"]!["Title"]!!,
rawMsg["msg"]!["Text"]!!,
rawMsg["msg"]!["Time"]!!],
forKeys: ["Title", "Text", "Time"])
} catch {
adminMsg = nil
}
But I get this warning:
"no calls to throwing function occur within 'try' expression"
Does this mean I have no choice but to crash if an item is missing from the dictionary? I can't trap it and let my code gracefully tell the sender they sent me an invalid NSDictionary (unless I check it all out item-by-item in code)?
I can't trap it and let my code gracefully tell the sender they sent me an invalid NSDictionary
You cannot trap a fatal error, no. What you can do is catch the missing elements by using optional binding.
guard let title = message["Title"],
let text = message["Text"],
let time = message["Time"]
else {
// Missing data; inform caller
}
This is how you gracefully tell the client that there's something wrong with the data. You could return nil, an empty dictionary, or throw an error: whichever suits you best.
To make this properly Swifty, you should first define your data:
typealias RawMessage = Dictionary<String, [String : AnyObject]>
/** Important keys in a `RawMessage` */
enum RawMessageKey : String
{
case message = "msg"
case title = "Title"
case text = "Text"
case time = "Time"
}
enum RawMessageError : ErrorType
{
/** The `RawMessage` has no "message" key */
case NoMessage
/** The `RawMessage` is missing an expected inner key */
case MissingKey
}
Then your extraction function. This uses optional binding, not force unwrapping, to check that the key "msg" is present. Force unwrapping failure cannot be "caught"; that's not what it's for.
If the key is not present, you signal that to the caller by throwing your own error. Then use further optional binding to get the rest of the items. If any are missing, again throw an error.
/** Pull important values from `RawMessage` and repackage as `NSDictionary` */
func extractAdminMessage(rawMsg: RawMessage) throws -> NSDictionary
{
guard let message = rawMsg[String(RawMessageKey.message)] else {
throw RawMessageError.NoMessage
}
let keys = [String(RawMessageKey.title),
String(RawMessageKey.text),
String(RawMessageKey.time)]
guard let title = message[String(RawMessageKey.title)],
let text = message[String(RawMessageKey.text)],
let time = message[String(RawMessageKey.time)]
else {
throw RawMessageError.MissingKey
}
return NSDictionary(objects: [title, text, time],
forKeys: keys)
}
If you prefer, using flatMap could be an alternative to the stacked unbind:
let keys = [String(RawMessageKey.title),
String(RawMessageKey.text),
String(RawMessageKey.time)]
let objects = keys.flatMap { message[$0] }
guard objects.count == keys.count else {
throw RawMessageError.MissingKey
}
Here, any missing value will be dropped by flatMap; then if there aren't the same number of keys as objects, signal your caller.
I came up with this:
let rawMsg : NSDictionary =
["msg":["Title":"sometitle", "Text":"sometext", "Time":"sometime"]]
let adminMsg = NSMutableDictionary()
if let msg = rawMsg["msg"] as? NSDictionary {
for key in ["Title", "Text", "Time"] {
adminMsg[key] = msg[key]
}
}
That works even if a key is missing from the "msg" dictionary. You won't crash at any point during that, not matter how malformed rawMsg may be.
(It would be better if adminMsg were a Swift dictionary rather than a Cocoa NSDictionary, but at least this seems to cover the original problem domain.)
A few suggestions
Stop using NSDictionary and use Swift dictionary instead
Don't use exceptions to manage control flow
The force unwrap ! produce a fatal error, you cannot catch it
What you should do instead
if let
msg = rawMsg["msg"],
title = msg["Title"],
text = msg["Text"],
time = msg["Time"] {
let adminMsg:[String:Any] = ["Title": title, "Text": text, "Time": time]
}

How do you store a dictionary on Parse using swift?

I am very new to swift and I don't know Obj C at all so many of the resources are hard to understand. Basically I'm trying to populate the dictionary with PFUsers from my query and then set PFUser["friends"] to this dictionary. Simply put I want a friends list in my PFUser class, where each friend is a PFUser and a string.
Thanks!
var user = PFUser()
var friendsPFUser:[PFUser] = []
var friendListDict: [PFUser:String] = Dictionary()
var query = PFUser.query()
query!.findObjectsInBackgroundWithBlock {
(users: [AnyObject]?, error: NSError?) -> Void in
if error == nil {
// The find succeeded.
println("Successfully retrieved \(users!.count) users.")
// Do something with the found objects
if let users = users as? [PFUser] {
friendsPFUser = users
for user in friendsPFUser{
friendListDict[user] = "confirmed"
}
user["friends"] = friendListDict //this line breaks things
user.saveInBackground()
}
} else {
// Log details of the failure
println("Error: \(error!) \(error!.userInfo!)")
}
}
To be clear, this code compiles but when I add
user["friends"] = friendListDict
my app crashes.
For those who might have this issues with. "NSInternalInconsistencyException" with reason "PFObject contains container item that isn't cached."
Adding Objects to a user (such as arrays or dictionaries) for security reasons on Parse, the user for such field that will be modified must be the current user.
Try signing up and using addObject inside the block and don't forget do save it!
It helped for a similar problem I had.