I've been using Stripe iOS SDK for a while now and everything is clear regarding the implementation. Since our app is going to support App Clips on iOS 14, we are reducing the binary size and therefore decided to remove Stripe iOS SDK as well.
So my question here is if I can somehow send payment requests via the API and omitting the Stripe SDK altogether?
p.s.: It looks like I need to implement the /tokens endpoint passing the card data. Is there any example of the request to be made?
I've managed to solve this situation and here is the solution if anyone is interested. Here are the steps to make this happen:
Prepare a request model
import Foundation
import PassKit
struct StripeTokenRequest: Encodable {
let pkToken: String
let card: Card
let pkTokenInstrumentName: String?
let pkTokenPaymentNetwork: String?
let pkTokenTransactionId: String?
init?(payment: PKPayment) {
guard let paymentString = String(data: payment.token.paymentData, encoding: .utf8) else { return nil }
pkToken = paymentString
card = .init(contact: payment.billingContact)
pkTokenInstrumentName = payment.token.paymentMethod.displayName
pkTokenPaymentNetwork = payment.token.paymentMethod.network.map { $0.rawValue }
pkTokenTransactionId = payment.token.transactionIdentifier
}
}
extension StripeTokenRequest {
struct Card: Encodable {
let name: String?
let addressLine1: String?
let addressCity: String?
let addressState: String?
let addressZip: String?
let addressCountry: String?
init(contact: PKContact?) {
name = contact?.name.map { PersonNameComponentsFormatter.localizedString(from: $0, style: .default, options: []) }
addressLine1 = contact?.postalAddress?.street
addressCity = contact?.postalAddress?.city
addressState = contact?.postalAddress?.state
addressZip = contact?.postalAddress?.postalCode
addressCountry = contact?.postalAddress?.isoCountryCode.uppercased()
}
}
}
Use JSONEncoder and set keyEncodingStrategy to .convertToSnakeCase.
Create a POST request against https://api.stripe.com/v1/tokens endpoint where you need to url encode parameters. If you are using Alamofire, you need to set encoding to URLEncoding.default.
Parse response. I use JSONDecoder with the following model:
import Foundation
struct StripeTokenResponse: Decodable {
let id: String
}
Create a payment
StripeTokenResponse.id is the thing you need to pass to the backend where the payment will be processed. This is the same step as you'll do when using the SDK.
You can check Strip checkout, it allows you to present a payment page in web format without any Stripe SDK on the client side.
Related
I am trying to read Personal Details (Blood group, Age, Gender) of Healthkit but unable to request for that.
As per Apple Doc here:
HealthKit provides five characteristic types: biological sex, blood
type, birthdate, Fitzpatrick skin type, and wheelchair use. These
types are used only when asking for permission to read data from the
HealthKit store.
But i can't make add HKCharacteristicType in authorisation request.
I have run Apple Sample Project which requests for:
HKQuantityTypeIdentifier.stepCount.rawValue,
HKQuantityTypeIdentifier.distanceWalkingRunning.rawValue,
HKQuantityTypeIdentifier.sixMinuteWalkTestDistance.rawValue
But when I add
HKCharacteristicTypeIdentifier.bloodType.rawValue
HKCharacteristicTypeIdentifier.dateOfBirth.rawValue
The permission screen does not asks for DOB and Blood Type. See Image:
Configuration: Simulator iOS 15.4 and Xcode 13.3
Anyone knows that if we can access Personal Data of HealthKit or not. Please help me out.
This is happening because bloodType and dateOfBirth are of type HKCharacteristicType
when you call this, the compactMap operation will not include your types
private static var allHealthDataTypes: [HKSampleType] {
let typeIdentifiers: [String] = [
HKQuantityTypeIdentifier.stepCount.rawValue,
HKQuantityTypeIdentifier.distanceWalkingRunning.rawValue,
HKQuantityTypeIdentifier.sixMinuteWalkTestDistance.rawValue,
HKCharacteristicTypeIdentifier.bloodType.rawValue,
HKCharacteristicTypeIdentifier.dateOfBirth.rawValue
]
return typeIdentifiers.compactMap { getSampleType(for: $0) }
}
check getSampleType:
func getSampleType(for identifier: String) -> HKSampleType? {
if let quantityType = HKQuantityType.quantityType(forIdentifier: HKQuantityTypeIdentifier(rawValue: identifier)) {
return quantityType
}
if let categoryType = HKCategoryType.categoryType(forIdentifier: HKCategoryTypeIdentifier(rawValue: identifier)) {
return categoryType
}
return nil
}
your types won't fall into any of these if let, so this function will return nil. You must change the code so you are able to use HKCharacteristicTypeIdentifier as well.
EDIT: An easy way to do this is changing the readDataTypes in HealthData class to:
static var readDataTypes: [HKObjectType] {
return allHealthDataTypes + [
HKObjectType.characteristicType(forIdentifier: .dateOfBirth)!,
HKObjectType.characteristicType(forIdentifier: .bloodType)!
]
}
You can only request read authorization for the HKCharacteristicTypes, not share authorization. Update your code to add these 2 data types only to the readDataTypes variable. Right now you are requesting both read & share for the characteristic types, which is why they are not appearing on the authorization sheet.
I get JSON from API and most of the keys are decoded out of the box.
Example API response:
[
{ "href":"http:\/\/audiomachine.com\/",
"description":"Audiomachine",
"extended":"Music for videos",
"shared":"yes",
"toread":"no",
"tags":"creative video music"
},
{ "href": "https:\/\/www.root.cz\/clanky\/nekricte-na-disky-zvuk-i-ultrazvuk-je-muze-poskodit-nebo-zmast-cidla\/",
"description":"Nek\u0159i\u010dte na disky: zvuk i\u00a0ultrazvuk je m\u016f\u017ee po\u0161kodit nebo zm\u00e1st \u010didla - Root.cz",
"extended":"Added by Chrome Pinboard Extension",
"shared":"yes",
"toread":"no",
"tags":"root.cz ril hardware webdev"
},
{ "href": "https:\/\/www.premiumbeat.com\/blog\/10-apple-motion-tutorials-every-motion-designer-watch\/",
"description":"10 Apple Motion Tutorials Every Motion Designer Should Watch",
"extended":"For people used to working with FCPX, sometimes learning Apple Motion can be much easier than learning After Effects. While the two programs are different, there\u2019s certainly a lot of overlap in terms of functionality and creative potential. The following ten Apple Motion tutorials are great examples of just that.\r\n\r\nWhether you are someone new to Apple Motion or a seasoned veteran looking to up your skills, here are ten must watch Apple Motion tutorials.",
"shared":"yes",
"toread":"no",
"tags":"apple apple_motion video creative effects"
}
]
I store the each item in following struct.
struct Link: Codable {
let href: URL
let description: String
var tags: String
}
which is then decoded with something like
URLSession.shared.dataTask(with:url!, completionHandler: {(data, response, error) in
guard let data = data, error == nil else { return }
do {
let decoder = JSONDecoder()
let decodedData = try decoder.decode([Link].self, from: data)
From API data I use href, description and tags. Tags is a bit troublemaker, because I want to use it in code as an Array of Strings, but in the JSON it is retrieved as a String containing all the tags separated by space. As this is my first project, I use to learn iOS development, I have almost no idea how to solve this.
I was able to get close by googling computed properties, so it could be something like
struct Link: Codable {
let href: URL
let description: String
var tags: String {
return tagsFromServer.components(separatedBy: " ")
}
}
, but I have two problems with this solution.
I have strong feeling, this closure would be called every time the code access .tags. I would rather do it once upon the JSON retrieval and after that just read product of that.
I have absolutely no idea how to pass "the original tags I've got from server" / tagsFromServer to the closure.
I would be grateful for filling up my gaps in terminology or a nice video tutorial on this topic, also.
I recommend to write a custom initializer, decode tags as String and split it:
struct Link: Decodable {
let href: URL
let description: String
let tags: [String]
private enum CodingKeys: String, CodingKey { case href, description, tags }
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
href = try container(URL.self, forKey: .href)
description = try container(String, forKey: .description)
let tagsFromServer = try container(String.self, forKey: .tags)
tags = tagsFromServer.components(separatedBy: " ")
}
}
Alternatively you can use the computed property but then you have to add the CodingKeys
struct Link: Decodable {
let href: URL
let description: String
let tagsFromServer : String
private enum CodingKeys: String, CodingKey { case href, description, tagsFromServer = "tags" }
var tags: [String] {
return tagsFromServer.components(separatedBy: " ")
}
}
I'm using custom authorisation with the Uber iOS SDK, and having trouble creating the AccessToken in my iOS code. This is the response I get from my server with what appears to be a valid access token:
{
"access_token":"token here",
"expires_in":2592000,
"token_type":"Bearer",
"scope":"all_trips request",
"refresh_token":"refresh token here",
"last_authenticated":0
}
I then pass this to the AccessToken initialiser, like so:
let jsonString = //response from server as above
let accessToken = AccessToken(tokenString: jsonString)
My access token is created (ie. non-nil), but none of the relevant property are populated.
accessToken //non-nil
accessToken.expirationDate //nil
accessToken.refreshToken //nil
Strangely, accessToken.tokenString contains the original jsonString from above.
Am I doing something wrong?
Edit
Digging through the AccessToken.swift source file of the Uber project, I find this:
#objc public init(tokenString: String) {
super.init()
self.tokenString = tokenString
}
It seems like it never actually creates the refreshToken etc.
The tokenString is meant to be just the access token itself, as you observed. If you want to parse the JSON itself, I would suggest using the fact that the model conforms to the Decodable protocol, and pass in your JSON through that method.
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .secondsSince1970
let accessToken = try? decoder.decode(AccessToken.self, from: jsonData)
// If you need to convert a string to data, use String.data(using: .utf8)!
Thanks in advance for any advice!
I'm setting up some unit tests in swift for iOS development. The method requires a call to the keychain to create an authToken to successfully run the function. I'm not sure how to approach creating a unit test for this kind of environment. Do I mock up a local file that I can use to bypass the authentication? Do I try to skip the authentication step entirely?
The function I'm testing is a private function as well and I'm having a hard time conceptualizing how I can test it through the public methods. Here is the code I'm working with:
override func viewDidLoad() {
super.viewDidLoad()
self.setMyDoctorsNavBarTitle()
self.setBackgroundWaterMark()
self.getDoctors()
//self.refreshControl?.addTarget(self, action: #selector(MyDoctorsViewController.refresh(_:)), forControlEvents: UIControlEvents.ValueChanged)
}
private func getDoctors() {
let authToken: [String: AnyObject] = [ "Authorization": keychain["Authorization"]!, // creates an authToken with the current values stored in
"UUID": keychain["UUID"]!, "LifeTime": keychain["LifeTime"]! ] // the keychain
RestApiManager.sharedInstance.postMyDocs(authToken) { (json, statusCode) in // passes the created authToken to postMyDocs in RestAPI to see if
if statusCode == 200 { // the token matches what's on the server
if let results = json["Doctors"].array { // If the credentials pass, we grab the json file and create an array of Doctors
for entry in results {
self.buildDoctorObject(entry) // Doctors information is parsed into individual objects
}
}
} else if statusCode == 401 {
/* If statucCode is 401, the user's AuthToken has expired. The historical AuthToken data will be removed from the iOS KeyChain and the user will be redirected to the login screen to reauthorize with the API
*/
self.keychain["Authorization"] = nil
self.keychain["UUID"] = nil
self.keychain["LifeTime"] = nil
let loginController = self.storyboard?.instantiateViewControllerWithIdentifier("LoginViewController") as! LoginViewController
NSOperationQueue.mainQueue().addOperationWithBlock {
self.presentViewController(loginController, animated: true, completion: nil)
}
} else if statusCode == 503 {
print("Service Unavailable Please Try Again Later")
}
}
}
private func buildDoctorObject(json: JSON){
let fName = json["FirstName"].stringValue
let lName = json["LastName"].stringValue
let city = json["City"].stringValue
let phNum = json["PhoneNumber"].stringValue
let faxNum = json["FaxNumber"].stringValue
let state = json["State"].stringValue
let addr = json["Address"].stringValue
let img = json["Image"].stringValue
let zip = json["Zip"].stringValue
let tax = json["Taxonomy"].stringValue
let docObj = DoctorObject(fName: fName, lName: lName, addr: addr, city: city, state: state, zip: zip, phNum: phNum, faxNum: faxNum, img: img, tax: tax)
self.docs.append(docObj)
self.tableView.reloadData()
}
I want to be able to Unit Test the getDoctors() and buildDoctorObject() functions, but I can only do that indirectly through viewDidLoad() since they're private.
I want to be able to test that the statusCode is being brought down correctly from the server and the appropriate steps take place if it comes back as 200 or 401.
I'm not necessarily looking for complete code, but simply a way to approach the problem. If you know of resources that might be helpful I would be grateful. I'm very new to this and I tried looking into resources online but couldn't find anything. A lot of what I found was you don't want to test private functions directly, and isn't advised to change the functions to public for the sake of testing.
Thanks again for looking into it!
Sean W.
Define that private method in the Test Class, with the same signature. Just try to call that method it will call your actual class method.
I'm trying to design an API helper function for an app. The idea is that I'll be able to call the function from a viewController, using code such as:
let api = APIController();
api.request("get_product_list")
api.delegate = self
Here's the class so far:
import Foundation
protocol APIControllerProtocol {
func didReceiveAPIResults(originalRequest: String, status: Bool, data: String, message: String)
}
class APIController {
var delegate: APIControllerProtocol?
let url = "https://example.co.uk/api.php"
let session = NSURLSession.sharedSession()
let appID = "EXAMPLEAPPID";
let deviceID = "EXAMPLEDEVICE"
func request(req:String)-> Void {
let urlString = "\(url)?request=\(req)"
let combinedUrl = NSURL(string: urlString)
let request = NSMutableURLRequest(URL: combinedUrl!)
request.HTTPMethod = "POST"
let stringPost="app_id=\(appID)&device_id=\(deviceID)"
let data = stringPost.dataUsingEncoding(NSUTF8StringEncoding)
request.timeoutInterval = 60
request.HTTPBody=data
request.HTTPShouldHandleCookies=false
let task = session.dataTaskWithRequest(request) {
(data, response, error) -> Void in
do {
let jsonData = try NSJSONSerialization.JSONObjectWithData(data!, options:NSJSONReadingOptions.MutableContainers ) as! NSDictionary
let statusInt = jsonData["status"]! as! Int
let status = (statusInt == 1)
let data = String(jsonData["data"])
let message = String(jsonData["message"])
self.delegate?.didReceiveAPIResults(req,status: status,data: data,message: message)
} catch _ {
print("ERROR")
}
}
task.resume()
}
}
The difficulty I'm having is that the 'data' parameter might one of the following:
A string / number, such as the number of purchases a customer has made
An array of items, such as a list of products
A dictionary, such as the customer's details
I've set the data parameter to String as that allowed me to do some testing, but then converting it back into something usable for a tableView got very messy.
Are there any experts on here that can advise me the best way to do this? Perhaps showing me how I'd use the results in a cellForRowAtIndexPath method? Here's an example response from the API, in case it's useful:
{
"status":1,
"message":"",
"cached":0,
"generated":1447789113,
"data":[
{"product":"Pear","price":0.6},
{"product":"Apple","price":0.7},
{"product":"Raspberry","price":1.1}
]
}
One function doing too many things at once makes a really messy code. Also you don't want too many if statements or enums - your View controller will grow really fast.
Id suggest splitting request and parse logic. Your API class would be then responsible only for requests. It would return data to another class, that would be responsible for parsing. Then in the Parser class you could add methods like toDictionary() or toArray(), toArrayOfClasses() and so on. That would be the basic API structure.
If you want to expand it a little bit, you could add another class layer that would handle all that logic so your View Controller doesn't know if it uses API or another data source - this way you could easy implement new things in the future, like Core Data or migrate from your API class to some framework, maybe Parse.com - this layer gives you flexibility.
Example structure:
API - requests
Parser - parsing API response
DataManager - Send request to API and return parsed response.
or if you don't want this third point, you can just request & parse in the view controller.