I'm pretty new to iOS Programming, and I'm stuck on this little portion here.
I'm basically trying to use Stripe in my iOS application. The user inputs an amount they wish to pay, press NEXT and they input their card details. All of this is going well so far - I can get the AddCardViewController to show up, and input their card details.
The code makes it all the way to verifying the card token and accepting the payment; which is wonderful, but then the problem comes that the AddCardViewController doesn't go away afterward.
When I was trying it out on a demo, it worked perfectly fine, but on my app it doesn't work at all! The app just gets stuck there on the AddCardViewController. Hitting cancel does nothing, there's no display message, and submitting the card details again just double bills the user.
What am I doing wrong?
For further info, this is my code:
StripeClient.shared.sendPostRequest(with: token, amount: amount, description: description) { result in
switch result {
// 1
case .success:
completion(nil)
let alertController = UIAlertController(title: "Congrats",
message: "Your payment was successful!",
preferredStyle: .alert)
let alertAction = UIAlertAction(title: "OK", style: .default, handler: { _ in
self.navigationController?.popViewController(animated: true)
})
alertController.addAction(alertAction)
self.present(alertController, animated: true)
self.view.makeToast("Your payment was successful! We will be sending you a receipt to your email shortly.")
// 2
case .failure(let error):
completion(error)
}
}
I made my custom post request because I couldn't get the one offered by the demo to work, but I can say with 100% certainty that the code enters case .success successfully - I tested this already. But just in case, this is my .sendPostRequest method:
func sendPostRequest(with token: STPToken, amount: Double, description: String, completion: #escaping (Result) -> Void) {
//declare parameter as a dictionary which contains string as key and value combination. considering inputs are valid
let params: [String: Any] = [
"stripeToken": token.tokenId,
"amount": amount,
"currency": Constants.defaultCurrency,
"description": description
]
//create the url with URL
let url = URL(string: "<this-is-my-url.com>")! //change the url
//create the session object
let session = URLSession.shared
//now create the URLRequest object using the url object
var request = URLRequest(url: url)
request.httpMethod = "POST" //set http method as POST
do {
request.httpBody = try JSONSerialization.data(withJSONObject: params, options: .prettyPrinted) // pass dictionary to nsdata object and set it as request body
} catch let error {
print(error.localizedDescription)
}
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
request.addValue("application/json", forHTTPHeaderField: "Accept")
//create dataTask using the session object to send data to the server
let task = session.dataTask(with: request as URLRequest, completionHandler: { data, response, error in
guard error == nil else {
return
}
guard let data = data else {
return
}
do {
//create json object from data
//print(data)
if let json = try JSONSerialization.jsonObject(with: data, options: .mutableContainers) as? [String: Any] {
//print(json)
// handle json...
//print(json["response"]!)
if let responseString = try json["response"] as? String {
if responseString == "SUCCESS" {
completion(Result.success)
} else {
completion(Result.failure(IntParsingError.overflow))
}
}
}
} catch let error {
print(error.localizedDescription)
}
})
task.resume()
}
I would really, really, REALLY appreciate the help!
1- You need
DispatchQueue.main.async {
completion(Result.success)
}
As session.dataTask(with: runs in background thread
2- For this to run
self.navigationController?.popViewController(animated: true)
verify the vc is embedded inside a navigation and you presented it with push not present func of the previous vc
Edit:
When you present do
self.dismiss(animated: true, completion: nil)
Related
In my project i have all POST method JSON URL's, so i need to create one separate function where i need to pass url and parameters and i need to handle response as well .. Now I want to use that function in all viewcontrollers with differnt urls and parameters... but i am unable to create separate function for JSON
I am writing same code for all viewcontrollrs with different urls and jsonpostParameters like below
func loginService(){
let url = URL(string: "https://e/api/login")!
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
let jsonpostParameters = LoginData(jsonrpc: "2.0", params: (PostLogin(email: nameTf.text!, password: passwordTf.text!, device_id: "2")))
do {
let jsonBody = try JSONEncoder().encode(jsonpostParameters)
request.httpBody = jsonBody
} catch {
print("Error while encoding parameter: \(error)")
}
let session = URLSession.shared
let task = session.dataTask(with: request) { [self] (data, response, error) in
guard let data = data else {return}
do{
let jsonModel = try JSONDecoder().decode(Employees.self, from: data)
print("new model \(jsonModel.result)")
DispatchQueue.main.sync{
if jsonModel.error != nil{
let controller = UIAlertController(title: "Alert", message: "Your email is not verified", preferredStyle: .alert)
let ok = UIAlertAction(title: "OK", style: .default, handler: nil)
let cancel = UIAlertAction(title: "Cancel", style: .cancel, handler: nil)
controller.addAction(ok)
controller.addAction(cancel)
self.present(controller, animated: true, completion: nil)
}
else{
let vc = UIStoryboard.init(name: "Main", bundle: Bundle.main).instantiateViewController(withIdentifier: "ProfileViewController") as? ProfileViewController
vc?.userData = jsonModel
vc?.tokenKey = jsonModel.result!.token
self.navigationController?.pushViewController(vc!, animated: true)
}
}
print("the error is \(error)")
}catch{ print("Error while decoding: \(error.localizedDescription)") }
}
task.resume()
}
how to write all code in one function with different urls and different parameters and handle the response from JSON, to call that function in all view controllers, pls do help with code
Here check this protocol
protocol Networking {
func fetch(_ url: String, _ params:[String : Any], completion: #escaping (Data?, Error?) -> Void)
}
extension Networking {
func fetch(_ url: String, _ params:[String : Any], completion: #escaping (Data?, Error?) -> Void) {
//create the url with URL
let url = URL(string: url)!
//now create the URLRequest object using the url object
var urlRequest = URLRequest(url: url)
urlRequest.httpMethod = "POST" //set http method as POST
do {
urlRequest.httpBody = try JSONSerialization.data(withJSONObject: params, options: .prettyPrinted) // pass dictionary to nsdata object and set it as request body
} catch let error {
print(error.localizedDescription)
}
// Set this in header if required
urlRequest.addValue("application/json", forHTTPHeaderField: "Content-Type")
urlRequest.addValue("application/json", forHTTPHeaderField: "Accept")
URLSession.shared.dataTask(with: urlRequest) { data, response, error in
if let error = error {
completion(nil, error)
return
}
guard let data = data else {
preconditionFailure("No error was received but we also don't have data...")
}
completion(data, nil)
}.resume()
}
}
And use this as below example
class SampleViewController: UIViewController, Networking {
override func viewDidLoad() {
super.viewDidLoad()
let url = "https://e/api/login"
let jsonpostParameters: [String: Any] = [:]
self.fetch(url, jsonpostParameters) { (data: Data?, error: Error?) in
guard let dt = data else { return }
// convert data to JSON
print(dt)
}
}
}
I'm trying to create a iOS app that processes credit card payments using the Square API.
I'm using a Heroku server as the backend.
My issue is when I try to process the credit card payment using the Card Entry view controller. I'm getting a NSCocoaErrorDomain issue code 3840 saying the "JSON text did not start with array or object and option to allow fragments not set."
Here is the snippet of code I used to set up the card entry form:
let theme = SQIPTheme()
theme.errorColor = .red
theme.tintColor = Color.primaryAction
theme.keyboardAppearance = .light
theme.messageColor = Color.descriptionFont
theme.saveButtonTitle = "Pay"
let cardEntryForm = SQIPCardEntryViewController(theme: theme)
return cardEntryForm
And here is the snippet of code I used to process the payment:
ChargeApi.processPayment(cardDetails.nonce) { (transactionID, errorDescription) in
guard let errorDescription = errorDescription else {
// No error occured, we successfully charged
completionHandler(nil)
return
}
}
Also here is the ChargeApi (code that is doing the processing of the payment):
class ChargeApi {
static public func processPayment(_ nonce: String, completion: #escaping (String?, String?) -> Void) {
let url = URL(string: Constants.Square.CHARGE_URL)!
var request = URLRequest(url: url)
request.httpMethod = "POST"
let json = ["nonce": nonce]
let httpBody = try? JSONSerialization.data(withJSONObject: json)
print(json)
request.addValue("Application/json", forHTTPHeaderField: "Content-Type")
request.httpBody = httpBody
URLSession.shared.dataTask(with: request) { data, response, error in
if let error = error as NSError?{
if error.domain == NSURLErrorDomain {
DispatchQueue.main.async {
completion("", "Could not contact host")
}
} else {
DispatchQueue.main.async {
completion("", "Something went wrong")
}
}
} else if let data = data {
do {
let json = try JSONSerialization.jsonObject(with: data, options: []) as! [String: Any]
if let httpResponse = response as? HTTPURLResponse, httpResponse.statusCode == 200 {
DispatchQueue.main.async {
completion("success", nil)
}
} else {
DispatchQueue.main.async {
completion("", json["errorMessage"] as? String)
}
}
} catch {
DispatchQueue.main.async {
completion("", "Failure")
}
}
}
}.resume()
}
I believe the way it works, is that after submission the credit card info is wrapped up in a value called a "nonce" and it is sent to the Sqaure API to be processed.
The API looks for this "nonce" in the following json format ["nonce": (some nonce value)].
However after successfully following the steps in the InAppPaymentsSample example:
https://developer.squareup.com/docs/in-app-payments-sdk/quick-start/step-2
I'm trying to something similar with my test app as far as talking to the Heroku server and sending test card information to the server, however I'm getting this NSCocoaErrorDomain issue.
Please help.
I have a project where I send and get data back from an API. Sending and getting it works fine.
My goal is to wait a response (ie: yes|no) from the server and depending on it, proceed with the segue or not and show an alert if no.
For this part I have:
a tableview view with a list of clients and a button to add new client.
a detail view where I add/edit a client
on detail view there is a save button with a segue to the tableview view
The function saveClient() gets the data and makes a request to the server
func saveClient() -> Bool {
let name = txtName.text ?? ""
let address = txtAddress.text ?? ""
let city = txtCity.text ?? ""
let province = txtProvince.text ?? ""
let postal_code = txtPostalCode.text ?? ""
meal = Client(name:name, client_id: "", postal_code: postal_code, province: province, city: city, address: address)
var jsonData = Data()
let jsonEncoder = JSONEncoder()
do {
jsonData = try jsonEncoder.encode(meal)
}
catch {
}
print("a1")
var success: Bool
success = false
makeRequestPost(endpoint: "http://blog.local:4711/api/clients/add",
requestType: "POST",
requestBody: jsonData,
completionHandler: { (response : ApiContainer<Client>?, error : Error?) in
if let error = error {
print("error calling POST on /todos")
print(error)
return
}
let b = (response?.meta)!
print(b.sucess)
if(b.sucess == "yes") {
success = true
}
else
{
DispatchQueue.main.async(execute: {
let myAlert = UIAlertController(title: "Error", message: "Error creating Client", preferredStyle: .alert)
let okAction = UIAlertAction(title: "Ok", style: .default, handler: nil)
myAlert.addAction(okAction)
self.present(myAlert, animated: true, completion: nil)
})
return
}
} )
return success
}
on this same controller:
override func shouldPerformSegue(withIdentifier identifier: String, sender: Any?) -> Bool {
guard let button = sender as? UIBarButtonItem, button === btnSave else {
return false
}
if !saveClient() {
print("no sir")
return false
}
print("yes sir")
return true
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
super.prepare(for: segue, sender: sender)
guard let button = sender as? UIBarButtonItem, button === btnSave else {
return
}
}
request function:
func makeRequestPost<T>(endpoint: String,
requestType: String = "GET",
requestBody: Data, completionHandler: #escaping (ApiContainer<T>?, Error?) -> ()) {
guard let url = URL(string: endpoint) else {
print("Error: cannot create URL")
let error = BackendError.urlError(reason: "Could not create URL")
completionHandler(nil, error)
return
}
var urlRequest = URLRequest(url: url)
let session = URLSession.shared
urlRequest.httpMethod = "POST"
urlRequest.httpBody = requestBody
urlRequest.addValue("application/json", forHTTPHeaderField: "Content-Type")
urlRequest.addValue("application/json", forHTTPHeaderField: "Accept")
let task = session.dataTask(with: urlRequest, completionHandler: {
(data, response, error) in
guard let responseData = data else {
print("Error: did not receive data")
completionHandler(nil, error)
return
}
guard error == nil else {
completionHandler(nil, error!)
return
}
do {
let response = try JSONDecoder().decode(ApiContainer<T>.self, from: responseData)
completionHandler(response, nil)
}
catch {
print("error trying to convert data to JSON")
print(error)
completionHandler(nil, error)
}
})
task.resume()
}
On the console i get:
a1
no sir
yes
The correct would be:
a1
yes
yes sir
Final notes:
I tried some examples w semaphores... but it didn't work.
I'm using a var named meal on other places and haven't changed it to clients. However, it doesn't interfere on that part of the code
You said:
My goal is to wait a response (ie: yes|no) from the server and depending on it, proceed with the segue or not
While I understand the natural appeal of that approach, the reality is that you never should "wait" (especially in shouldPerformSegue). Your UI will freeze (especially notable if the user has poor Internet connectivity), your app is susceptible to being killed by the watchdog process that looks for frozen apps, etc.
So, rather than performing the segue and having shouldPerformSegue try to wait for the network response to see if it's OK to proceed, do it the other way around: You can write code that performs the query and if everything is good, only then initiate segue programmatically.
I have craeted a class called db_conn and inside this calass I have the following functions :
public func login (email : String , password : String ) -> String {
var result = ""
let url = URL(string: API.url_login.rawValue)!
var request = URLRequest(url: url)
request.setValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type")
request.httpMethod = "POST"
let postString = "email=\(email)&password=\(password)"
request.httpBody = postString.data(using: .utf8)
let task = URLSession.shared.dataTask(with: request) { data, response, error in
guard let data = data, error == nil else {
print("error=\(String(describing: error))")
return
}
if let httpStatus = response as? HTTPURLResponse, httpStatus.statusCode != 200 {
print("statusCode should be 200, but is \(httpStatus.statusCode)")
print("\(String(describing: response))")
}
let responseString = String(data: data, encoding: .utf8)
result = String(responseString!)
//print("responseString = \(responseString!)")
}
task.resume()
return result
}
and then in my login viewController class I try to use it like ==>
#IBAction func login_button_action(_ sender: UIButton) {
let email = email_text.text
let passw0rd = password_text.text
if (email != "" && passw0rd != ""){
let db_connect = db_conn()
if (db_connect.login(email: email!, password: passw0rd!) == nil ){
print("it's empty ")
}else {
print("not empty \(db_connect.login(email: email!, password: passw0rd!))")
}
}else {
let alert = UIAlertController(title: "Error", message: "Dont leave any of the spaces blank , they all must be filled up , try again ! ", preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "Ok", style: UIAlertActionStyle.default, handler: nil))
self.present(alert, animated: true, completion: nil)
}
when I run the project it says that the value for db_connect.login(email: email!, password: passw0rd!) is not empty but it doesnt print it out .
(However in my db_conn calss I can easily print the responseString and I get the returned JSON in String format .
Any idea where am I making my mistake ? And how can I possibly fix it ?
Making a network request with URLSession runs asynchronously, which means that iOS put it onto a background thread and continues the current thread immediately before the download is finished. Therefore, when you return result, it's still an empty string. What you probably want to do is pass a completion closure to your login method, and call that with the relevant data when it's finished downloading
I am new to web development and Swift
I created a web api based on ASP.NET and I connected my ios app so I can do GET, POST, PUT, DELETE.
When I send GET request with specific ID number
I get output in Xcode as following:
Data:
Optional("{\"Id\":1,\"UserName\":\"codeinflash\",\"UserPassword\":\"Wldnrodxxxx\",\"UserEmail\":\"codeinflash#gmail.com\",\"Rebate\":0.00,\"MemCom\":123.44}")
Here is function in Swift:
//GET /api/account/{id}
#IBAction func GetAccount(_ sender: Any) {
let _accountIdNumber = AccountIdNumber.text
if (_accountIdNumber!.isEmpty){
createAlert(title: "Warning", message: "Account ID # is required")
return
}
let restEndPoinst: String = "http://tresmorewebapi2.azurewebsites.net/api/account/" + _accountIdNumber!;
guard let url = URL(string: restEndPoinst) else {
print("Error creating URL")
return
}
var urlRequest = URLRequest(url: url)
urlRequest.setValue("application/json", forHTTPHeaderField: "Content-Type")
// api key need urlRequest.setValue(<#T##value: String?##String?#>, forHTTPHeaderField: "APIKey")
let config = URLSessionConfiguration.default
let session = URLSession(configuration: config)
var userEmail = ""
var rebate = ""
var memcom = ""
let task = session.dataTask(with: urlRequest, completionHandler:
{
(data, response, error) in
print("Error:")
print(error)
print("response:")
print(response)
print("Data:")
print(String(data: data!, encoding: String.Encoding.utf8))
//////////////I think I need logic here to filter the data
userEmail = data.substring of find emailaddress
rebate = data.substring find users' rebate
memcom = same logic
then show alert window with his info and I will show his info on next page which is a Dashboard page view
})
task.resume()
}
Honestly I am not sure the Data is JSON data but the output in Xcode is in string.
My purpose is get uer's data and store in local variables that passes them to next view(Dashboard) in ios Swift.
Thank you!
Added
Here is the task getting data from web api in login fuction swift
let config = URLSessionConfiguration.default
let session = URLSession(configuration: config)
var userEmail = ""
var rebate = ""
var memcom = ""
let task = session.dataTask(with: urlRequest, completionHandler:
{
(data: Data?, response: URLResponse?, error: Error?) in
print("Error:")
print(error)
print("response:")
print(response)
print("Data:")
print(String(data: data!, encoding: String.Encoding.utf8))
let json = try? JSONSerialization.jsonObject(with: data!) as! [String: AnyObject] ?? [:];
userEmail = json?["UserEmail"] as? String ?? ""
createAlert(title: "Got User Eamil Address!", message: userEmail)
})
task.resume()
But I get nothing in my alert view. The alert view working fine I tested.
Here is my createAlert fuction in Swift
func createAlert(title:String, message:String){
let alert = UIAlertController(title: title, message: message, preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "OK", style: .default, handler: { (anction) in
alert.dismiss(animated: true, completion: nil)}))
self.present(alert, animated: true, completion: nil)
}
You have answer in comments already, but this is less "swifty" way, more readable for someone new to Swift (pay attention and try to understand what is going on and how you can make this more compact):
do {
guard let unwrappedData = data else{
//data is nil - handle case here
return //exit scope
}
guard let dictionary = try JSONSerialization.jsonObject(with: unwrappedData) as? [String:AnyObject] else {
//you cannot cast this json object to dictionary - handle case here
return //exit scope
}
userEmail = dictionary["UserEmail"]
DispatchQueue.main.async { [weak self] in
self?.createAlert(title: "Got User Eamil Address!", message: userEmail)
}
} catch _ {
//handle error
}