I have STRIPE integration in my ios application.
I am able to generate token using cards details entered by users.
I send this TOKEN to server for payment process.
That's all fine !
Problem , is I want to create a STRIPE Customer using its API.
How to do this , does SDK provide anything for this ?
OR passing 'Authorization_KEY' and 'STRIPE_TEST_PUBLIC_KEY' in header is the way ?
OR I need to implement whole 'OAuth 2.0' for this ?
Please help !
Thank you !
I don't think you can create a Stripe Customer with the Public Key. I'm quite sure Secret key is required for this request and so it should probably be handled on the server instead of the client app.
Yes, nabeel is correct, the customer should be created by the server instead of the client app. Although, if you want to risk it, you can do it like this...
class StripeUtils {
//Feed in the STPCardParams to create a Stripe token.
func generateToken(with params: STPCardParams, completion: #escaping (Error?, STPToken?) -> ()) {
STPAPIClient.shared().createToken(withCard: params) { (token, error) in
completion(error, token)
}
}
//Pass the token and user email address to get the STPCustomer
func createUserWith(email: String, token: STPToken?, completion: #escaping (Error?, STPCustomer?) -> ()) {
guard let token = token else {
print("Token can not be nil")
completion(*error*, nil)
return
}
let headers = ["Authorization": "Bearer \(Constants.STRIPE_SECRET_KEY)"] //The secret key
let body = ["email": email, "source": token.tokenId] as [String : Any]
var paramString = String()
body.forEach({ (key, value) in
paramString = "\(paramString)\(key)=\(value)&"
})
let params = paramString.data(using: .utf8)
//URLStrings.stripe_createUser is "https://api.stripe.com/v1/customers"
//The APIManager is a class that takes urlString, params(HTTP body) and headers(HTTP headers) to get initialized.
//(You can use Alamofire or whatever you use to handle APIs)
//Instance of APIManager has a method called 'perform' with the URLSession's completion block (Data?, URLResponse?, Error?) -> ()
let manager = APIManager(urlString: URLStrings.stripe_createUser.description, params: params, headers: headers)
manager.perform { (data, response, error) in
//Use STPCustomerDeserializer intead of standard JSONSerialization to let Stripe hanlde the API response.
let object = STPCustomerDeserializer.init(data: data, urlResponse: response, error: error)
completion(object.error, object.customer)
//That's it, you'll have a STPCustomer instance with stripeId if there were no errors.
}
}
}
Related
I want to send the facebook access token to the AWS Cognito and then receive an authorization token which can further be sent as an Authorization header in HTTP Put request.
However, I always get "unauthorized" response from the AWS end point.
When I try to print :
credentialsProvider.credentials().continueOnSuccessWith(executor: AWSExecutor.default()) { (task) -> Any? in
print(task.error)
return true
}
I get the following output:
Optional(Error Domain=com.amazonaws.AWSJSONBuilderErrorDomain Code=4 "serialized object is neither a valid json Object nor NSData object: {
IdentityPoolId = "******";
Logins = {
"graph.facebook.com" = "<FBSDKAccessToken: *******>";
};
}" UserInfo={NSLocalizedDescription=serialized object is neither a valid json Object nor NSData object: {
IdentityPoolId = "*****+*";
Logins = {
"graph.facebook.com" = "<FBSDKAccessToken: ******>";
};
}})
This is my code:
import AWSCognito
class FacebookProvider: NSObject, AWSIdentityProviderManager {
func logins() -> AWSTask<NSDictionary> {
if let token = FBSDKAccessToken.current() {
return AWSTask(result: [AWSIdentityProviderFacebook:token])
}
return AWSTask(error:NSError(domain: "Facebook Login", code: -1 , userInfo: ["Facebook" : "No current Facebook access token"]))
}
}
class API {
..............
public func putOrder(when fbLogin: Bool, _ order: Order, onSuccess: #escaping(JSON) -> Void,
on Failure: #escaping(Error)-> Void) {
let credentialsProvider = AWSCognitoCredentialsProvider(regionType: .EUCentral1 ,
identityPoolId:"*****", identityProviderManager:FacebookProvider())
let configuration = AWSServiceConfiguration(region: AWSRegionType.EUCentral1, credentialsProvider: credentialsProvider)
AWSServiceManager.default().defaultServiceConfiguration = configuration
let url = "\(serverURL)\(API.loginOrderPath)"
let urlRequest: NSMutableURLRequest = NSMutableURLRequest(url: NSURL(string: url)! as URL)
urlRequest.httpMethod = API.apiMethodPut
urlRequest.setValue("application/json; charset=utf-8", forHTTPHeaderField: "Content-Type")
urlRequest.setValue("\(credentialsProvider.credentials())", forHTTPHeaderField: "Authorization")
do {
var json: JSON
json = ["companyId": order.companyId, "drinks": order.drinksId, "payment": order.payment, "tip": order.tip]
urlRequest.httpBody = try json.rawData()
let task = URLSession.shared.dataTask(with: urlRequest as URLRequest, completionHandler: {data, response, error -> Void in
if error != nil {
Failure(error!)
} else {
if let response = try? JSON(data: data!) {
onSuccess(response)
} else {
}
}
})
task.resume()
} catch _ {
}
}
}
Expected Result: JSON response from the AWS Server
Actual Result : unauthorised
The reason why your are receiving an unauthorized response from API Gateway is double :
credentialsProvider.credentials() is not serialized to JSON and can not be "as is" for the authorization headers.
Looks like you are trying to manually call API Gateway, by managing your self the low level details of the URL Request. I don't see code to add a signature to the request. All authenticated API Gateway requests must be signed (see https://docs.aws.amazon.com/apigateway/api-reference/making-http-requests/) and the Authorization header must contain the credentials used to compute the signature.
Managing the low level details of AWS Signature is not trivial. You should not write code to do that but use the AWS iOS SDK instead. In particular, if you're trying to call API Gateway with Cognito User Pool authorisation, have a look at this example : https://docs.aws.amazon.com/apigateway/latest/developerguide/how-to-generate-sdk-ios-swift.html.
The API Gateway console will generate the client side code required to run this example (see https://docs.aws.amazon.com/apigateway/latest/developerguide/genearte-ios-sdk-of-an-api.html)
This should remove a lot of boiler plate from your code, making it easier to read and to maintain.
The flow posted in your comment above, the iOS SDK will take care of 2/ 3/ and 4/ steps for you - automatically.
How to translate my login user URLSession code into Siesta framework code? My current attempt isn't working.
I've looked at the example in the GithubBrowser but the API I have doesn't work like that.
The issue is that the user structure is kind of split by how the endpoint in the API I'm consuming works. The endpoint is http://server.com/api/key. Yes, it really is called key and not user or login. Its called that by the authors because you post a user/pass pair and get a key back. So it takes in (via post) a json struct like:
{"name": "bob", "password": "s3krit"}
and returns as a response:
{"token":"AEWasBDasd...AAsdga"}
I have a SessionUser struct:
struct SessionUser: Codable
{
let name: String
let password: String
let token: String
}
...which encapsulates the state (the "S" in REST) for the user. The trouble is name & password get posted and token is the response.
When this state changes I do my:
service.invalidateConfiguration() // So requests get config change
service.wipeResources() // Scrub all unauthenticated data
An instance is stored in a singleton, which is picked up by the configure block so that the token from the API is put in the header for all other API requests:
configure("**") {
// This block ^ is run AGAIN when the configuration is invalidated
// even if loadManifest is not called again.
if let haveToken = SessionManager.shared.currentUser?.token
{
$0.headers["Authorization"] = haveToken
}
}
That token injection part is already working well, by the way. Yay, Siesta!
URLSession version
This is bloated compared to Siesta, and I'm now not using this but here is what it used to be:
func login(user: SessionUser, endpoint: URL)
{
DDLogInfo("Logging in: \(user.name) with \(user.password)")
let json: [String: Any] = ["name": user.name, "password": user.password]
let jsonData = try? JSONSerialization.data(withJSONObject: json)
var request = URLRequest(url: endpoint)
request.httpMethod = "POST"
request.httpBody = jsonData
_currentStatus = .Unknown
weak var welf = self
let task = URLSession.shared.dataTask(with: request) { data, response, error in
guard let data = data else {
handleLogin(error: error, message: "No data from login attempt")
return
}
let jsonData:Any
do {
jsonData = try JSONSerialization.jsonObject(with: data, options: [])
}
catch let jsonDecodeError {
handleLogin(error: jsonDecodeError, message: "Could not get JSON from login response data")
return
}
guard let jsonDecoded = jsonData as? [String: Any] else {
handleLogin(error: error, message: "Could not decode JSON as dictionary")
return
}
guard let token = jsonDecoded["token"] as? String else {
handleLogin(error: error, message: "No auth token in login response")
return
}
let newUser = SessionUser(name: user.name, password: "", token: token)
welf?.currentUser = newUser
welf?.saveCurrentSession()
welf?._currentStatus = .LoggedIn
DDLogInfo("User \(newUser.name) logged in")
loginUpdate(user: newUser, status: .LoggedIn, message: nil, error: nil)
}
task.resume()
}
Siesta Version
Here is my attempt right now:
func login(user: String, pass: String, status: #escaping (String?) -> ())
{
let json = [ "name": user, "password": pass]
let req = ManifestCloud.shared.keys.request(.post, json: json)
req.onSuccess { (tokenInfo) in
if let token = tokenInfo.jsonDict["token"] as? String
{
let newUser = SessionUser(name: user, password: pass, token: token)
self.currentUser = newUser
}
status("success")
}
req.onFailure { (error) in
status(error.userMessage)
}
req.onCompletion { (response) in
status(nil)
}
}
Its sort of working, but the log in credentials are not saved by Siesta and I've had to rig up a new notification system for login state which I'd hoped Siesta would do for me.
I want to use Siesta's caching so that the SessionUser object is cached locally and I can use it to get a new token, if required, using the cached credentials. At the moment I have a jury-rigged system using UserDefaults.
Any help appreciated!
The basic problem here is that you are requesting but not loading the resource. Siesta draws a distinction between those two things: the first is essentially a fancied-up URLSession request; the second means that Siesta hangs on to some state and notifies observers about it.
Funny thing, I just answered a different but related question about this a few minutes ago! You might find that answer a helpful starting point.
In your case, the problem is here:
let req = ManifestCloud.shared.keys.request(.post, json: json)
That .request(…) means that only your request hooks (onSuccess etc.) receive a notification when your POST request finishes, and Siesta doesn’t keep the state around for others to observe.
You would normally accomplish that by using .load(); however, that creates a GET request and you need a POST. You probably want to promote your POST to be a full-fledge load request like this:
let keysResource = ManifestCloud.shared.keys
let req = keysResource.load(using:
keysResource.request(.post, json: json))
This will take whatever that POST request returns and make it the (observable) latestData of ManifestCloud.shared.keys, which should give you the “notification system for login state” that you’re looking for.
I am building an application with Swift-iOS and deployd as backend Deployd. I'm trying to figure out Twitter OAuth. Basically I would want to know how can I send my server a token and let dpd-passport save it in a user collection.
Twitter API is returning access token very fine. Here's my code:
TwitterClient.SharedInstance.requestSerializer.removeAccessToken()
//where TwitterClient is a BDBOAuth1SessionManager instance
TwitterClient.SharedInstance.fetchRequestTokenWithPath("oauth/request_token", method: "GET", callbackURL: NSURL(string: "cptwitter://oauth"), scope: nil, success: { (requestToken: BDBOAuth1Credential!) -> Void in
print(requestToken.token)
let authURL = NSURL(string: "https://api.twitter.com/oauth/authorize?oauth_token=\(requestToken.token)")
UIApplication.sharedApplication().openURL(authURL!)
}) { (error: NSError!) -> Void in
print("failed to get the token request")
}
I'm struggling with getting this to work to make request to my API. Without a token works, but when I try to add additional headers, things turn to be complicated, for me.
First, the structure.
one class called: APIAsyncTask that makes the requests
one class called APIParams, just a data holder to send parameters to the APIAsyncTask class.
one class called DatabaseAPI that makes that builds the parameters, and send that to the APIAsyncTask class.
DatabaseAPI
func someMethod()
{
let task = APIAsyncTasks()
task.registerCallback { (error, result) -> Void in
print("Finished task, back at DatabaseAPI")
}
let params2 = APIParams(request: .GET, apiPath: "Posts/1", apiToken: "4iTX-56w")
task.APIrequest(params2)
}
APIAsyncTask
This part is for fixing another error, because manager was not global, the task got cancelled quickly.
var manager : Manager!
init(authenticatedRequest : Bool, token: String?)
{
manager = Alamofire.Manager()
print("Pre \(manager.session.configuration.HTTPAdditionalHeaders?.count)")
if(authenticatedRequest && token != nil)
{
var defaultHeaders = Alamofire.Manager.sharedInstance.session.configuration.HTTPAdditionalHeaders!
defaultHeaders["Authorization"] = "bearer \(token)"
let configuration = Manager.sharedInstance.session.configuration
configuration.HTTPAdditionalHeaders = defaultHeaders
manager = Alamofire.Manager(configuration: configuration)
}
print("Post \(manager.session.configuration.HTTPAdditionalHeaders?.count)")
}
After some decision making, it comes down to this part.
private func GetRequest(url: String!,token : String?, completionHandler: (JSON?, NSURLRequest?, NSHTTPURLResponse?, NSError?) -> () ) -> ()
{
print("Begin Get Request")
if(token != nil)//if token is not nil, make authenticated request
{
print("just before request: \(manager.session.configuration.HTTPAdditionalHeaders?.count)")
manager.request(.GET, url, parameters: nil, encoding: .JSON).responseJSON { (request, response, json, error) in
print("Get Request (authenticated), inside alamofire request")
var resultJson : JSON?
if(json != nil)
{
resultJson = JSON(json!)
}
completionHandler(resultJson, request, response, error)
}
}
else
{
//working part without token
So as the code is now, I get an error on completing:
Mattt himself gives the answer of using Alamofire.Manager.sharedInstance.session.configuration.HTTPAdditionalHeaders
, so that should be fine...
I suspect it has something to do with the multiple threads, according to this blog. Or, since it is something about CFNetwork, it could be because my API does not use SSL? I disabled NSAppTransportSecurity
I'm kind of new to swift, so examples would be really appreciated! Thankyou!
So the majority of your code looks solid.
The error leads me to believe that CFNetwork is having difficulty figuring out how to compute the protection space for the challenge. I would also assume you are getting a basic auth challenge since you are attaching an Authorization header.
Digging through your logic a bit more with this in mind led me to see that your not attaching your token to the string properly inside the Authorization header. You need to do the following instead.
defaultHeaders["Authorization"] = "bearer \(token!)"
Otherwise your Authorization header value is going to include Optional(value) instead of just value.
That's the only issue I can see at the moment. If you could give that a try and comment back that would be great. I'll update my answer accordingly if that doesn't actually solve your problem.
Best of luck!
You can add your headers in your request with Alamofire 2 and Swift 2.
For an example: go to example
We are using PayPal future payments in our IOS app. We need to know email ID of the account that has authorized future payment. How can we fetch email ID of the user who has authorized future payment. Current API operation for approval returns only authorization token.
I'm assuming by "future payments" you're referring to Preapproved Payments..??
Setup an IPN solution and make sure to an IPNNotificationURL in your Preapproval API request. The IPN will include more details about the transaction including the payer email address.
Here is a list of the variables you can expect from a Preapproval profile getting created. You'll notice the "sender_email" parameter, which is what you'd be looking for.
Here's a sample of an actual IPN I got in the sandbox after processing a Preapproval request.
Array
(
[max_number_of_payments] => 100
[starting_date] => 2015-03-01T00:00:21.000-08:00
[pin_type] => NOT_REQUIRED
[max_amount_per_payment] => 20.00
[currency_code] => USD
[sender_email] => guy.louzon-buyer#gmail.com
[verify_sign] => AFcWxV21C7fd0v3bYYYRCpSSRl31AiHQSQchSGUInXdtl6zomfkZ7H4C
[test_ipn] => 1
[date_of_month] => 0
[current_number_of_payments] => 0
[preapproval_key] => PA-2M0807730Y425554F
[ending_date] => 2015-12-31T23:59:21.000-08:00
[approved] => true
[transaction_type] => Adaptive Payment PREAPPROVAL
[day_of_week] => NO_DAY_SPECIFIED
[status] => ACTIVE
[current_total_amount_of_all_payments] => 0.00
[current_period_attempts] => 0
[charset] => windows-1252
[payment_period] => 0
[notify_version] => UNVERSIONED
[max_total_amount_of_all_payments] => 2000.00
)
Ichathan, you'll want to utilize the Profile Sharing feature of the mSDK to get the customer attributes and pass in the Future Payment scope within that to also gain consent for those. The available scopes you can use for Profile Sharing are listed out in the PayPalOAuthScopes.h file of the iOS SDK.
This answer is correct but not detailed.
The Profile Sharing Mobile Integration allows the user to consent to future payments as well as gets email and other information in one login flow. Here's the snippet we used:
func profileController() -> PayPalProfileSharingViewController {
PayPalMobile.preconnectWithEnvironment(PayPalEnvironmentSandbox)//PayPalEnvironmentNoNetwork)
let scope: Set<String> = Set([kPayPalOAuth2ScopeEmail, kPayPalOAuth2ScopeFuturePayments])
let controller = PayPalProfileSharingViewController(scopeValues: scope, configuration: self.paypalConfiguration!, delegate: self)
return controller!
}
func payPalProfileSharingViewController(profileSharingViewController: PayPalProfileSharingViewController, userDidLogInWithAuthorization profileSharingAuthorization: [NSObject : AnyObject]) {
self.processAuthorization(profileSharingAuthorization)
}
func userDidCancelPayPalProfileSharingViewController(profileSharingViewController: PayPalProfileSharingViewController) {
self.delegate?.didFailPayPalConsent()
}
func processAuthorization(authorization: [NSObject: AnyObject]) {
if let authCode = authorization["response"]?["code"] as? String {
self.delegate?.didSucceedPayPalConsent(authCode)
}
else {
self.delegate?.didFailPayPalConsent()
}
}
Edit: The mobile controller gives you the auth token that has permissions for profile information, but you have to make another call from your server side code for that information:
https://developer.paypal.com/docs/api/#get-user-information
This is how I did it.
The profile sharing Paypal Profile Sharing gives us the Auth Token
This particular delegate function gets called
func payPalProfileSharingViewController(profileSharingViewController: PayPalProfileSharingViewController, userDidLogInWithAuthorization profileSharingAuthorization: [NSObject : AnyObject]) {
self.processAuthorization(profileSharingAuthorization)
}
After authToken we need to hit some server side APIs . We can achieve this through app side as well. I have hit server side apis from client side
First step is to make a basic Auth Request it will return us a Refresh as well as Access Token. Get Access Token
func generateAccessToken(authCode : String ,block : completionHandler){
let parameters = ["grant_type" : "authorization_code", "response_type" :"token","redirect_uri" : "urn:ietf:wg:oauth:2.0:oob","code":authCode]
let username = AppConstants().kPayPalUserName //APP_ID
let password = AppConstants().kPayPalSecret
let credentialData = "\(username):\(password)".data(using: String.Encoding.utf8)!
let base64Credentials = credentialData.base64EncodedString(options: [])
let headers = ["Authorization": "Basic \(base64Credentials)"]
let customerURL = AppConstants().kPayPalUrl
Alamofire.request(customerURL,
method: .post,
parameters: parameters,
encoding: URLEncoding.default,
headers:headers)
.validate()
.responseJSON { response in
switch response.result {
case .success(let value):
KVNProgress.dismiss(completion: {
block?(true, value as! Dictionary<String, Any>) // get the accessToken
})
// BasicFunctions.displayAlert("Success", needDismiss: false, title: "Task Created Successfully")
case .failure(let responseError):
KVNProgress.dismiss(completion: {
if (responseError != nil) {
BasicFunctions.displayAlert(SERVER_ERROR)
// let json = JSONSerialization
// block!(false,responseError as! Dictionary<String, Any>)
}else{
BasicFunctions.displayAlert(SERVER_ERROR)
}
})
}
}
}
Using the access Token we need to hit another CURL request and it will give us all the user information Get User Profile Information
Now using this request we can get complete user info. The access Token was generated from Basic Auth Token
func getUserProfileInfo(accessToken : String,block : completionHandler){
KVNProgress.show()
let parameters = ["":""]
let headers = ["Authorization": "Bearer " + accessToken]
let customerURL = "https://api.sandbox.paypal.com/v1/identity/openidconnect/userinfo/?schema=openid"
Alamofire.request(customerURL, method: .post, parameters: parameters, encoding: JSONEncoding.default, headers: headers).responseJSON { (response) in
switch response.result {
case .success(let value):
KVNProgress.dismiss(completion: {
block?(true, value as! Dictionary<String, Any>)
})
// BasicFunctions.displayAlert("Success", needDismiss: false, title: "Task Created Successfully")
case .failure(let responseError):
KVNProgress.dismiss(completion: {
if (responseError != nil) {
BasicFunctions.displayAlert(SERVER_ERROR)
// let json = JSONSerialization
// block!(false,responseError as! Dictionary<String, Any>)
}else{
BasicFunctions.displayAlert(SERVER_ERROR)
}
})
}
}
}
Note : Make sure in your app settings in Paypal You have allowed access to email or other user information
Disclaimer : This project was only for a POC so I am not sure whether we are violating the PCI compliance by hitting server side APIs from client side