confused on how to create the Stripe ephemeral key in the APIClient.swift class - swift

So for the last 2 days I've been stumped on how to implement this Stripe API, it's by far been the hardest thing to wrap my head around with. So I decided to integrate the Stripe functionality using Firebase and Cloud Functions and I've been seeing that it's server-less which is great.
I've been trying to follow this article on iOS Stripe API integration and this article showing how to create the cloud functions involving Stripe and I so far have been able to create a Stripe customer upon new user creation. After that, I'm pretty much lost on how to do what I want to do next, which is create ephemeral keys.
I have this function I snagged from another SO post:
exports.createEphemeralKeys = functions.https.onRequest((req, res) => {
var api_version = req.body.api_version;
var customerId = req.body.customerId;
if (!api_version) {
res.status(400).end();
return;
}
stripe.ephemeralKeys.create(
{ customer: customerId },
{ stripe_version: api_version },
function(err, key) {
return res.send(key);
});
});
And this is the method in the MyAPIClient.swift file:
func createCustomerKey(withAPIVersion apiVersion: String, completion: #escaping STPJSONResponseCompletionBlock) {
let url = self.baseURL.appendingPathComponent("ephemeral_keys")
var urlComponents = URLComponents(url: url, resolvingAgainstBaseURL: false)!
urlComponents.queryItems = [URLQueryItem(name: "api_version", value: apiVersion)]
var request = URLRequest(url: urlComponents.url!)
request.httpMethod = "POST"
let task = URLSession.shared.dataTask(with: request, completionHandler: { (data, response, error) in
guard let response = response as? HTTPURLResponse,
response.statusCode == 200,
let data = data,
let json = ((try? JSONSerialization.jsonObject(with: data, options: []) as? [String : Any]) as [String : Any]??) else {
completion(nil, error)
return
}
completion(json, nil)
})
task.resume()
}
Now this is where I get confused, since integrating Stripe with Firebase is server-less, what are we supposed to input in the baseURL? Currently the baseURL variable is empty, but I'm also getting thrown errors that .appendingPathComponent is not available as well. Forgive me if this is a dumb question, but I'd much rather look like a complete idiot and eventually figure out how to successfully integrate this API, than not ask anything at all. Thanks in advance.

The baseURL should be set to the URL where your Firebase functions live. Have a look at the Firebase documentation for invoking an HTTP function for details.
The URL will be something like this:
https://<region>-<project-id>.cloudfunctions.net/

Related

Simple post request in SwiftUI

I'm beginner in SwiftUI and I'm not familiar with variable management.
I'd like to send a very simple post request like this one with SwiftUI:
let full_path : String = "https://www.example.com/get_answer?my_message=current temperature"
I've tried with this piece of code but it didn't work.
if (URL(string: full_path) != nil) {
let url = URL(string: full_path)!
var request = URLRequest(url: url)
request.httpMethod = "POST"
var decodedAnswer = String("")
do {
let task = URLSession.shared.dataTask(with: url) { data, response, error in
print(response)
decodedAnswer = String(decoding: response, as: UTF8.self)
}
}
I have the following error:
Value of optional type 'URLResponse?' must be unwrapped to a value of
type 'URLResponse'
I don't know how to get the response.
How can I get the response from a simple Post request in SwiftUI?
Multiple issues here.
You are trying to decode the URLResponse object, but what you want is the data object in the decoder.
You seem to not know about optionals. I would refer you to the basic Apple tutorials about this topic. You can find it with your favorite search engine.
You are in an async context here. Everything inside the url datasession closure will be execute after your network request returns. The code in your function will be completed by that moment and your var decodedAnswer will be out of scope. So move it out of the function in to the class/struct.
You probably want something like this:
This should be defined in class scope or you won´t be able to use it:
var decodedAnswer: String = ""
This should be in a function:
let full_path: String = "https://www.example.com/get_answer?my_message=current temperature"
if let url = URL(string: full_path) {
var request = URLRequest(url: url)
request.httpMethod = "POST"
do {
let task = URLSession.shared.dataTask(with: url) { data, response, error in
//This converts the optionals in to non optionals that could be used further on
//Be aware this will just return when something goes wrong
guard let data = data, let response = response, error == nil else{
print("Something went wrong: error: \(error?.localizedDescription ?? "unkown error")")
return
}
print(response)
decodedAnswer = String(decoding: data, as: UTF8.self)
}
task.resume()
}
}

How to upload data using the new swift async/await methods?

The new features in Swift with async/await allow a better control about the process and a simplification in coding. But I cannot find out how this method can be applied for requests which go above a simple data reading. E.g. I need to pass a parameter in order to get a specific answer from a SQL database in the backend (accessed via php).
At first my code about the "standard" way to start with. This function reads all customer records and stores them into an account-array:
#available(iOS 15.0, *)
static func getCustomerListFromBackend() async throws -> [Account] {
let url = URL(string: "https://xxx.de/xxx/getCustomerList.php")!
let (data, _) = try await URLSession.shared.data(from: url)
var accounts: [Account] = []
accounts = try JSONDecoder().decode([Account].self, from: data)
return accounts
}
In order to make my question clear, now a piece of code in which the central statement does not work and exist. In this function I want to check whether a customer exists in the database and I need to pass the emailAddress as a parameter.
#available(iOS 15.0, *)
static func checkCustomerExistsInBackend(emailAddress: String) async throws -> String {
let url = URL(string: "https://xxx.de/xxx/checkCustomerexists.php")!
var request = URLRequest(url: url)
request.httpMethod = "POST"
var dataString = "EmailAddress=\(emailAddress)"
let dataD = dataString.data(using: .utf8)
// Statement which does not work but for which I need an alternative
let (data, _) = try await URLSession.shared.upload(request: request, data: dataD)
let answer = try JSONDecoder().decode(BackendMessage.self, from: data)
return answer.Message
}
Unfortunately there is no statement for URLSession.shared.upload(request: request, data: dataD) Until now (before async/await), I used URLSession.shared.uploadTask(with: request, from: dataD) and then used .resume() to process it. This method however gave me too many problems in controlling the right sequence of tasks in the app. Async/await could simplify this very much as in my first example.
So, is there a way to realize this? Any advice would be appreciated.
you could try using URLComponents something like:
func checkCustomerExistsInBackend(emailAddress: String) async throws -> String {
if let url = URL(string: "https://xxx.de/xxx/checkCustomerexists.php"),
var components = URLComponents(url: url, resolvingAgainstBaseURL: false) {
components.queryItems = [URLQueryItem(name: "EmailAddress", value: emailAddress)]
var request = URLRequest(url: components.url!)
request.httpMethod = "POST"
let (data, _) = try await URLSession.shared.data(for: request)
let answer = try JSONDecoder().decode(BackendMessage.self, from: data)
return answer.Message
}
throw URLError(.badURL)
}
My question was answered by Florian Friedrich's comment and workingdog's answer as well. To the later one I had to make a little adoption which I want to reflect here in this wrap up in case it can be helpful for someone with a similar problem. I show here 2 solutions to my problem with a few remarks.
Applying Florian's answer.
This was straightforward and worked right away:
static func checkCustomerExistsInBackend(emailAddress: String) async throws -> String {
let url = URL(string: "https://xxx.de/xxx/checkCustomerexists.php")!
var request = URLRequest(url: url)
request.httpMethod = "POST"
let dataString = "EmailAddress=\(emailAddress)"
let dataD = dataString.data(using: .utf8)
let (data, _) = try await URLSession.shared.upload(for: request, from: dataD!, delegate: nil)
let answer = try JSONDecoder().decode(BackendMessage.self, from: data)
return answer.Message
}
The proposal from workingdog:
Here I noticed that although the url appeared to be correctly set (ending with checkCustomerexists.php?EmailAddress=test#gmx.de), the parameter did not arrive in my php object. After some tests I found out that it works when I use GET instead of POST. So in my php file I changed the line $EmailAddress = $_POST[EmailAddress]; to $EmailAddress = $_GET['EmailAddress'];. (I am sure there is a good reason for this and I am just not experienced enough to recognize this.) Accordingly the code I use for workingdog's proposal is slightly adjusted:
func checkCustomerExistsInBackend3(emailAddress: String) async throws -> String {
if let url = URL(string: "https://xxx.de/xxx/checkCustomerexists.php"),
var components = URLComponents(url: url, resolvingAgainstBaseURL: false) {
components.queryItems = [URLQueryItem(name: "EmailAddress", value: emailAddress)]
var request = URLRequest(url: components.url!)
request.httpMethod = "GET"
let (data, _) = try await URLSession.shared.data(for: request)
let answer = try JSONDecoder().decode(BackendMessage.self, from: data)
return answer.Message
}
throw URLError(.badURL)
}

Returning parsed JSON data using Alamofire?

Hello new to Swift and Alamofire,
The issue i'm having is when I call this fetchAllUsers() the code will return the empty users array and after it's done executing it will go inside the AF.request closure and execute the rest.
I've done some research and I was wondering is this is caused by Alamofire being an Async function.
Any suggestions?
func fetchAllUsers() -> [User] {
var users = [User]()
let allUsersUrl = baseUrl + "users/"
if let url = URL(string: allUsersUrl) {
AF.request(url).response { response in
if let data = response.data {
users = self.parse(json: data)
}
}
}
return users
}
You need to handle the asynchrony in some way. This this means passing a completion handler for the types you need. Other times it means you wrap it in other async structures, like promises or a publisher (which Alamofire also provides).
In you case, I'd suggest making your User type Decodable and allow Alamofire to do the decoding for you.
func fetchAllUsers(completionHandler: #escaping ([User]) -> Void) {
let allUsersUrl = baseUrl + "users/"
if let url = URL(string: allUsersUrl) {
AF.request(url).responseDecodable(of: [User].self) { response in
if let users = response.value {
completionHandler(users)
}
}
}
}
However, I would suggest returning the full Result from the response rather than just the [User] value, otherwise you'll miss any errors that occur.

Using refresh token to get new authorization token and repeat failed api call

I have a general question about using tokens in swift to make api calls. The authorization token that is needed to make api calls expires every hour so I need a way to handle this in a generalized way for multiple api calls.
I'm facing an issue where if I get a 401 error I call a function to use the refresh token to get a new authorization token and I would like to re call the original function that gotten 401 error.
For example:
If I get a 401 error when I call getDetails() I want to call the getNewAuthToken() and after I get a new refresh token I want to call getDetails() again.
I want to do this in a way so that if I call any function getX() and I get a 401 error it calls getNewAuthToken() and then it calls the original function again getX()
What would be the best way to approach this without using any external libraries etc. Would the best way be using a sort of callback function ?
I have provided general code I've been implementing but as you can see when I get a 401 error it calls the getNewAuthToken() function but the original function is not called again. How can this code be modified to behave as needed?
import UIKit
import Combine
#Published var details: Details = nil
func getNewAuthToken(){
// here I request a token using the refresh token
}
func getDetails(){
self.getCurrentDetails{ details in
DispatchQueue.main.async {
self.details = details
}
}
}
func getCurrentDetails(_ completionHandler: #escaping (Details) -> ()) {
let url = "https://api.xxx.com/details"
guard let detailsURL = URL(string: url) else{
fatalError("URL not valid")
}
let authtoken = keychain.get("authtoken") ?? ""
var request = URLRequest(url: detailsURL)
request.httpMethod = "GET"
request.addValue("Bearer \(authtoken)", forHTTPHeaderField: "Authorization")
let session = URLSession.shared
let task = session.dataTask(with: request){
data, response, error in
let httpResponse = response as? HTTPURLResponse
// I request a new token
if(httpResponse?.statusCode == 401){
print("401")
self.getNewAuthToken()
return
}
do {
if(httpResponse?.statusCode != 200){
return
}
let decoder = JSONDecoder()
let details = try decoder.decode(Details.self, from:
data!)
completionHandler(details)
} catch let error2{
print(error2)
}
}
task.resume()
}
I don't think you need to separate function like "getX()" to handle this. All you need is a completion handler.
func getNewAuthToken(completionHandler: () -> Void) {
// here I request a token using the refresh token
completionHandler()
}
func someNetworkCall() {
getNewAuthToken {
someNetworkCall()
}
}

Saving CoreData to a Web Server with Swift 3.0

This question is related to: Swift Core Data Sync With Web Server.
I have followed the steps that have been mentioned in the question above however I am unable to apply the third step to my current project.
I currently have a class called Records
class Records {
static let shared = Records()
var records = [Record]()
let context = PersistenceServce.context
let request = NSFetchRequest<Record>(entityName: "Record")
func recordData() -> [Record] {
do {
records = try context.fetch(Record.fetchRequest())
}catch {
print("Error fetching data from CoreData")
}
return records
}
}
and here is how I display the data on my tableViewController.
func getData() {
records = Records.shared.recordData()
self.tableView.reloadData()
}
I do know how save data to a web server as this tutorial explains: https://www.simplifiedios.net/swift-php-mysql-tutorial/ as well as check for internet connection. However I am unsure how to apply it to the CoreData where there are multiple data involved.
If anyone could direct me to a solution or an explain how this can be achieved I'd very much appreciate it.
The question that you have linked is not trying to explain how to communicate with a web server. It is explaining how to store data in core data and tag/mark it in a way that you know which records have been sent to the web server or not.
So the Predicate will fetch all records that have not been sent to the web server and allow you to send them when you have an internet connection available.
Communicating with a web server can be a broad topic and will depend on your web server and API setup, so it is too much to explain here fully. I refer you to some free online resources that will help you understand networking in Swift.
Udacity - Networking with Swift
Ray Wenderlich - Alamofire Tutorial
Stack Overflow - Swift POST Request
Here is an example of a POST Request from the StackOverflow answer above
var request = URLRequest(url: URL(string: "http://test.tranzporthub.com/street45/customer_login.php")!)
request.httpMethod = "POST"
let postString = "user_id=chaitanya3191#gmail.com&password=123"
request.httpBody = postString.data(using: .utf8)
let task = URLSession.shared.dataTask(with: request) { data, response, error in
guard let data = data, error == nil else { // check for fundamental networking error
print("error=\(error)")
return
}
if let httpStatus = response as? HTTPURLResponse, httpStatus.statusCode != 200 { // check for http errors
print("statusCode should be 200, but is \(httpStatus.statusCode)")
print("response = \(response)")
}
let responseString = String(data: data, encoding: .utf8)
print("responseString = \(responseString)")
}
task.resume()
Using code similar to this, you should be able to send data to your web server, then your web server can do whatever it likes with it.
UPDATE:
To encode your parameters to JSON you can use the following code as a guide
var dictionary = [
"username": "Test User",
"password": "Password"
]
if let jsonData = try? JSONSerialization.data(withJSONObject: dictionary, options: []) {
// jsonData is a byte sequence, to view it you would need to convert to string
print(String(bytes: jsonData, encoding: String.Encoding.utf8))
}
Which would output:
Optional("{\"username\":\"Test User\",\"password\":\"Password\"}")
Note: you would send it as data, not the string version. so your code might look like this:
request.httpBody = jsonData