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
Related
I'm doing a small project to prepare for server linking, but I'm trying to save the token in Userdefault! I don't know how to code.
var headers: [String: String]? {
switch self {
case .signIn:
return nil
case .renewalToken:
guard let token = UserDefaults.standard.set("userID", forKey: "signIn") else{
return ["Authorization": "Bearer " + token]
}
default:
guard let token = UserDefaults.standard.set(<#T##value: Any?##Any?#>, forKey: <#T##String#>)
return ["Authorization": "Bearer " + token]
}
}
This code can be obtained by putting it in the code below using the header, but I don't know what to do with the token
func get(_ api: TargetType) -> DataRequest{
return AF.request(baseURI + api.path, method: .get, parameters: api.parameters, encoding: JSONEncoding.prettyPrinted, headers: api.headers, interceptor: nil)
}
First of all you should not store sensitive information into the user defaults. I would recommend you to use keychain. It's highly secure.
There are several Libraries which give simple and easy to use interfaces of keychain
SwiftyKeychainWrapper is one of them:
https://github.com/jrendel/SwiftKeychainWrapper
Update -
You need to access user default values using following code:
let token = UserDefaults.standard.string(forKey: “keyName”) ?? “”
Note: if token doesn't exist then you can just navigate user to the Login screen for authentication and regeneration of token.
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 have requested the current user and from the user have requested the saved tracks of the user. When making the call to the api it succeeds and I am able to convert the response into a Spotify track object, but when I access the fields of the object the values come back as the default values. I parsed this response in the same manner as I did to get the current user but it didn't work this time. Any ideas on the cause?
let savedTracksRequestHeaders: HTTPHeaders = [
"Authorization": "Bearer " + accessToken!,
]
let params = ["limit": "1"]
var userSong = SPTTrack.init()
Alamofire.request("https://api.spotify.com/v1/me/tracks", method:
.get, parameters: params, headers:
savedTracksRequestHeaders).responseJSON { response in
print("Saved Track Response")
debugPrint(response)
do {
// executes with no errors
try userSong = SPTTrack.init(from: response.data, with:
response.response)
print("user songs")
// when the variable is used all fields are default
// values
print(userSong.popularity)
} catch is Error {
print("load songs failed")
}
}
I'm trying to get the media liked by the logged in user. I do the authentication process and get an access token like this.
1312564049.dd97f3a.e9gw8d5516414d348c0b34f328e80fb1
I made sure to ask for public_content scope permission as well.
Then I call the /users/self/media/liked endpoint passing this token.
let urlString = "https://api.instagram.com/v1/users/self/media/liked"
let params = ["access_token": token]
Alamofire.request(.GET, urlString, parameters: params, encoding: .URL, headers: nil).responseJSON { response in
print(response.description)
}
But I get the following as the result.
{
data = (
);
meta = {
code = 200;
};
pagination = {
};
}
There are many photos liked by the user account I use. So I'm baffled why this returns empty. Is this because I'm in sandbox mode? Or something wrong with this endpoint? Because I checked /users/self and /users/self/media/recent and they both returned results.
Its because of the sanbox mode.
https://www.instagram.com/developer/sandbox/
Data is restricted to the 10 users and the 20 most recent media from each of those users
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.
}
}
}