I'm using Gigya as a single-sign-on system for my iOS app.
It is integrated and I can log in with both Twitter, Facebook and manual email registration.
As both Facebook and Twitter do not return mobile phone numbers, I'm appending this information after successful registration/login along with some other information like e-mail. I am able to successfully update fields in the profile like username, nickname etc, but not phones.
A description of the profile structure can be found here:
http://developers.gigya.com/020_Client_API/020_Accounts/010_Objects/Profile
So I figure to post:
{#"phones": #[#{#"number" : _phoneNumberTextfield.text}]}
as the profile content. Which is apparently alright, since the response has statusReason OK.
All good, and if I add other fields, they get updated. But when I retrieve the profile, there is no phone number there. I tried to append the field "type" as per the definition, but then I get: 400025 Write access validation error.
So the update call tells me everything is OK, but it isn't appending the number to the profile. Adding the type to each number entry in the array of #"phones" gives an access violation.
I've been through Gigya's API spec and can't find any working examples or even JSON examples of this situation; does anyone have a solution for this?
If you're retrieving the profile using accounts.getAccountInfo, make sure you include the "extraProfileFields = phones" parameter. The phones array will not be returned by default.
http://developers.gigya.com/037_API_reference/020_Accounts/accounts.getAccountInfo
The Gigya SDK on the server-side represents data as JSON objects, which have the ability to represent nested objects under a key or arrays.
In the case of the "profile.phone" property on the account, this is stored as an array of objects, as detailed below:
{
"profile": {
"phones": [
{ "type": "phone", "number": "8005551234" },
{ "type": "cell", "number": "8885551234" }
]
}
}
Typically, the when using Gigya's iOS API, it is common to maps these JSON concepts to the the NSMutableDictionary and NSMutableArray classes respectively and then to serialize the data using the NSJSONSerialization class.
So, for example, if we wanted to set the phone numbers on an account with Gigya like shown above, then you would need to accomplish this using something like the following code:
NSMutableDictionary *phone1 = [NSMutableDictionary dictionary];
[phone1 setObject:#"phone" forKey:#"type"];
[phone1 setObject:#"8005551234" forKey:#"number"];
NSMutableDictionary *phone2 = [NSMutableDictionary dictionary];
[phone2 setObject:#"cell" forKey:#"type"];
[phone2 setObject:#"8885551234" forKey:#"number"];
NSMutableArray *phones = [NSMutableArray array];
[phones addObject:phone1];
[phones addObject:phone2];
NSMutableDictionary *profile = [NSMutableDictionary dictionary];
[profile setObject:phones forKey:#"phones"];
NSError *error;
NSData *jsonData = [NSJSONSerialization dataWithJSONObject:profile
options:0
error:&error];
GSRequest *request = [GSRequest requestForMethod:#"accounts.setAccountInfo"];
[request.parameters setObject:jsonString forKey:#"profile"];
[request sendWithResponseHandler:^(GSResponse *response, NSError *error) {
if (!error) {
NSLog(#"Success");
// Success! Use the response object.
}
else {
NSLog(#"error");
// Check the error code according to the GSErrorCode enum, and handle it.
}
}];
Alternatively, you could construct a JSON string directly; but the above strategy tends to be much more flexible to any changes that need to be done when adding new properties.
Related
I'm fairly new to programming and I created an app to charge customers and would like to store their CC information and charge it at a later time. I've been going through all the tutorials and documentation and I am unable to follow how I can integrate this into my app. Do I need to know other technical skills such as Rest API, Curl, Ruby, etc to get this set up? All the guides and documentation is pointing to that direction. I don't really understand what GET/POST is for and how that fits into iOS Objective-C programming.
Any guidance on how to set this up would be tremendously appreciated. I've been stuck on this for some time now.
Parse's stripe API is not as complete as it could and should be. There are many features it does not include natively, but can be accomplished VIA an HTTP Request. I had to learn a little bit of Javascript, and HTTP request to get many features working. Of course your first instinct should tell you do NOT store a CC number on any device ever! Anytime you have a user input a CC number, immediately get a token and then that is all you will need to use.
Luckily stripe gives you the ability to save customers, and attached CC to customers, and then charge that customer in the future without getting the CC number again. Parse's api does not handle adding a CC to a customer so I added the feature myself.
So Step 1 and 2 Generate a Customer using Parse's API, and generate a Token from the CC information they enter again using Parse's API. If you need help with this, and the cloud code required let me know.
Step 3 Add a CC to a customer. I'm using a custom Customer object, but the main thing you really need is the stripe customerId which is customer.identifier in my code, and tokenID from your CC which in my case is token.tokenId. The response back will be a JSON string with the card information, I turn this into a Dictionary, and then create a STPCard from the dictionary. Also I show how to remove a card from a customer.
iOS Code:
+(void)addToken:(STPToken *)token toCustomerId:(NSString *)customerId completionHandler:(PFIdResultBlock)block
{
[PFCloud callFunctionInBackground:#"stripeUpdateCustomer" withParameters:#{#"customerId":customerId,#"data":#{#"card":token.tokenId}} block:block];
}
+ (void)removeCard:(STPCard *)card FromCustomer:(ELCustomer *)customer completion:(STPCardDeletionBlock)handler
{
if (!customer ||!customer.identifier || !card || !card.identifier || !handler) [NSException raise:#"RequiredParameter" format:#"Required Parameter Missing for deleting card from customer"];
[PFCloud callFunctionInBackground:#"stripeDeleteCardFromCustomer" withParameters:#{#"cardId":card.identifier,#"customerId":customer.identifier} block:^(id object, NSError *error)
{
NSDictionary *dict = nil;
NSError *jsonError = nil;
if (object && [object isKindOfClass:[NSString class]] && !error) {
dict = [NSJSONSerialization JSONObjectWithData:[object dataUsingEncoding:NSUTF8StringEncoding] options:kNilOptions error:&jsonError];
}
if (!jsonError && dict) {
handler(dict[#"id"],[dict[#"deleted"] boolValue],error);
}
else if(jsonError) handler(nil,NO,jsonError);
else handler(nil,NO,error);
}];
}
Cloud Code Required:
Parse.Cloud.define("stripeUpdateCustomer", function(request, response)
{
Stripe.Customers.update
(
request.params["customerId"],
request.params["data"],
{
success:function(results)
{
console.log(results["id"]);
response.success(results);
},
error:function(error)
{
response.error("Error:" +error);
}
}
);
});
Parse.Cloud.define("stripeDeleteCardFromCustomer", function(request, response)
{
Stripe.initialize(STRIPE_SECRET_KEY);
Parse.Cloud.httpRequest({
method:"DELETE",
//STRIPE_SECRET_KEY will be your stripe secrect key obviously, this is different from the public key that you will use in your iOS/Android side.
// STRIPE_API_BASE_URL = 'api.stripe.com/v1'
url: "https://" + STRIPE_SECRET_KEY + ':#' + STRIPE_API_BASE_URL + "/customers/" + request.params.customerId + "/cards/" + request.params.cardId,
success: function(httpResponse) {
response.success(httpResponse.text);
},
error: function(httpResponse) {
response.error('Request failed with response code ' + httpResponse.status);
}
});
});
iOS Code for applying a charge to a customer or token notice the required parameters in the dictionary are an amount in cents not dollars, a currency, and then either a customer or a tokenId. Note a customer can have many credit cards, but one of them is the active credit card. The active card is the card that will be charged when you charge a customer:
//Will attempt to charge customer, if no customer exists, or it fails to charge the custoemr it will attempt to charge a card token directly;
//*********Warning: This is the final step it will APPLY A CHARGE TO THE ACCOUNT.***************
-(void)processChargeThroughStripeWithCompletionHandler:(STPChargeCompletionHandler)handler
{
if (![self validForCardProcessing] && ![self validForCustomerProcessing]) {
handler(nil,[NSError errorWithDomain:MY_ERROR_DOMAIN code:elErrorCodeNoCustomerOrTokenID userInfo:[NSDictionary dictionary]]);
return;
}
[self processChargeThroughStripeUsingCustomerWithCompletionHandler:^(STPCharge *charge, NSError *error)
{
if (!error) handler(charge,error);
else{
[self processChargeThroughStripeUsingCardWithCompletionHandler:^(STPCharge *charge, NSError *error) {
handler(charge, error);
}];
}
}];
}
//Process payment using a customer to their active card. No token is required if customer exists with a card on record.
//*********Warning: This is the final step it will APPLY A CHARGE TO THE ACCOUNT.***************
-(void)processChargeThroughStripeUsingCustomerWithCompletionHandler:(STPChargeCompletionHandler)handler
{
if (!self.validForCustomerProcessing)
{
handler(self,[NSError errorWithDomain:MY_ERROR_DOMAIN code:elErrorCodeNoCustomerID userInfo:[NSDictionary dictionary]]);
return;
}
[PFCloud callFunctionInBackground:#"chargeToken" withParameters:[STPCharge dictionaryFromSTPChargeForProccessingUsingCustomer:self] block:^(id object, NSError *error)
{
if (!error)
{
[self initSelfWithDictionary:object];
NSLog(#"object:%#",object);
}
handler(self,error);
}];
}
//Process payment using a token that is attached to the charge, when complete self will be updated with the new charge information
//*********Warning: This is the final step it will APPLY A CHARGE TO THE ACCOUNT.***************
-(void)processChargeThroughStripeUsingCardWithCompletionHandler:(STPChargeCompletionHandler)handler
{
if (!self.validForCardProcessing)
{
handler(self,[NSError errorWithDomain:MY_ERROR_DOMAIN code:elErrorCodeNoTokenID userInfo:[NSDictionary dictionary]]);
return;
}
[PFCloud callFunctionInBackground:#"chargeToken" withParameters:[STPCharge dictionaryFromSTPChargeForProccessingUsingCard:self] block:^(id object, NSError *error)
{
if (!error)
{
[self initSelfWithDictionary:object];
}
handler(self,error);
}];
}
+ (NSDictionary *)dictionaryFromSTPChargeForProccessingUsingCard:(STPCharge *)charge
{
NSMutableDictionary *dictionary = [NSMutableDictionary dictionary];
dictionary[#"amount"] = charge.amountInCents;
dictionary[#"currency"] = charge.currency;
dictionary[#"card"] = charge.token.tokenId;
return dictionary;
}
+ (NSDictionary *)dictionaryFromSTPChargeForProccessingUsingCustomer:(STPCharge *)charge
{
NSMutableDictionary *dictionary = [NSMutableDictionary dictionary];
dictionary[#"amount"] = charge.amountInCents;
dictionary[#"currency"] = charge.currency;
dictionary[#"customer"] = charge.customer.identifier;
return dictionary;
}
Cloud code for charging a customer/token:
Parse.Cloud.define("chargeToken",function(request,response)
{
Stripe.initialize(STRIPE_SECRET_KEY);
Stripe.Charges.create
(
request.params,
{
success:function(results)
{
response.success(results);
},
error:function(error)
{
response.error("Error:" +error);
}
}
);
});
How are you storing their CC information to charge it at a later time? Before proceeding, you need to know if it is PCI compliant or not. At most, the only things you should be looking to store is the expiration date, last 4 digits, and an associated record object that Parse Stripe gives you that corresponds to that CC. Do not try to store the full CC.
As to your other questions:
Generally you need to know a web language to do something like this. Here is an example of a possible stack that I've seen in a situation like this:
iOS App -> sends request to Server (rails, python, php, etc) -> Will send request to 3rd party site
3rd party site response -> Server -> iOS app.
The point of the server is to intercept the call from the mobile App to Parse, and the response from Parse back to the mobile app. The reason for this is so you can have a "master" db of the transactions/states and can recover if the app is ever reinstalled on the user's phone. It also will let you store an identifier that points to the user's CC on parse stripe (I'm assuming).
You should really understand GET/POST as they are becoming a very basic feature of any iOS app. They are simply how you get/insert records from a server. Considering almost all of the popular apps have some kind of network connectivity embedded in them, it really is a core part of iOS programming IMO.
I want to create a json string to save in NSUserDefaults and then get it back from it.
I've already added RestKit to my project, to send and receive objects from the server.
However, now i want to flatten and save the data.
How do i use restkit to get a JSON string of my object?
I needed the same functionality (in my case to send he JSON as a multipart attribute), so after a while searching, I get the solution.
You can get the JSON data (and JSON string) using RestKit with that code, you have to pass the object you want to convert, and the mapping you want to use to convert.
RKRequestDescriptor *descriptorObject = ...
Custom *object = ...
NSDictionary *parametersForObject = [RKObjectParameterization parametersWithObject:object requestDescriptor:descriptorObject error:nil];
NSError *error;
NSData *jsonData = [NSJSONSerialization dataWithJSONObject:parametersForObject
options:NSJSONWritingPrettyPrinted //Pass 0 if you don't care about the readability of the generated string
error:&error];
NSString *jsonString;
if (! jsonData) {
NSLog(#"Got an error: %#", error);
}
else {
jsonString = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];
}
I have not find this documented, I just find it browsing the code, I am not sure if it is a public API or a private one. At least, it works for RestKit 0.20
You generally don't. RestKit serialises to JSON only as part of a network communication. In this case you're converting from an internal to external data format. In your case you want an internal data format stored for a while. In this case it's easier to just use NSJSONSerialization. To do this you do need to create a dictionary / array and then use dataWithJSONObject:options:error:.
Technically, to do what you ask you would use RKMappingOperation. This is done using the initWithSourceObject:destinationObject:mapping:, where the source object is the dictionary created from the JSON (JSONObjectWithData), the destination object is a new instance of the object targeted by the mapping and the mapping is your mapping to use.
From Kinvey documentation this is the method to use for querying users:
To query the user collection we recommend instead using
+[KCSUserDiscovery lookupUsersForFieldsAndValues:completionBlock:progressBlock:]. This
method allows you to supply a dictionary of exact matches for special
fields.
Fields for lookup:
KCSUserAttributeUsername
KCSUserAttributeSurname
KCSUserAttributeGivenname
KCSUserAttributeEmail
KCSUserAttributeFacebookId
[KCSUserDiscovery lookupUsersForFieldsAndValues:#{ KCSUserAttributeSurname : #"Smith"}
completionBlock:^(NSArray *objectsOrNil, NSError *errorOrNil) {
if (errorOrNil == nil) {
//array of matching KCSUser objects
NSLog(#"Found %d Smiths", objectsOrNil.count);
} else {
NSLog(#"Got An error: %#", errorOrNil);
}
}
progressBlock:nil];
But if I send empty dictionary, I get an error. So what to put in dictionary to get all the users?
Thank you guys, happy holidays
To get all the users, you can use the regular app data querying API.
For example,
KCSAppdataStore* store = [KCSAppdataStore storeWithCollection:[KCSCollection userCollection] options:nil];
[store queryWithQuery:[KCSQuery query] withCompletionBlock:^(NSArray *objectsOrNil, NSError *errorOrNil) {
//handle completion
} withProgressBlock:nil];
This will get a list of all the users the active user has permission to access.
Is it possible to implement the following? I want to create an iOS application which allows the user to see all their Facebook friends who already have this application in their Facebook list. Is it achievable via Facebook Connect or Graph API or maybe something else?
Yes, it's possible.
You will need to maintain your own database of who has installed the application.
When a user installs the application, they connect to Facebook through one of the APIs, get their userID, and submit it to your database with the flag that the user installed the application.
You then ask Facebook through one of its APIs for that user's list of friends' IDs, and then ask your database if any of those IDs have the associated flag set.
If you have followed the Facebook tutorial iOS Tutorial.
You should have access to a "facebook" object in your AppDelegate.m file. That "facebook" object will already have been registered with your specific application id.
You can then use it to query for the current user's friends who have already installed the application.
Here is the Facebook query to retrieve friend data:
NSString *fql = [NSString stringWithFormat:#"Select name, uid, pic_small from user where is_app_user = 1 and uid in (select uid2 from friend where uid1 = %#) order by concat(first_name,last_name) asc", _replace_this_with_the_fb_id_of_the_current_user_ ];
Yes you can do it with the new Facebook SDK 3.5 and greater (2013 June) using GraphAPI.
You can use the deviceFilteredFriends mutable array later as you like...
// The Array for the filtered friends
NSMutableArray *installedFilteredFriends = [[NSMutableArray alloc] init];
// Request the list of friends of the logged in user (me)
[[FBRequest requestForGraphPath:#"me/friends?fields=installed"]
startWithCompletionHandler:
^(FBRequestConnection *connection,
NSDictionary *result,
NSError *error) {
// If we get a result with no errors...
if (!error && result)
{
// here is the result in a dictionary under a key "data"
NSArray *allFriendsResultData = [result objectForKey:#"data"];
if ([allFriendsResultData count] > 0)
{
// Loop through the friends
for (NSDictionary *friendObject in allFriendsResultData) {
// Check if installed data available
if ([friendObject objectForKey:#"installed"]) {
[installedFilteredFriends addObject: [friendObject objectForKey:#"id"]];
break;
}
}
}
}
}];
Use Facebook login and then ask for the user's friends. You will only get those that have the requesting app installed (it used to return all friends with an 'installed' flag)
I'm trying to test my native iOS app and I've been having huge problems with test users. Basically it seems that the normal graph based way of creating test users doesn't work for native apps. When I try I get the following response from the server:
{
"error": {
"message": "(#15) This method is not supported for native apps",
"type": "OAuthException",
"code": 15
}
}
This seems to be supported by various posts on SO and a page on the old FB forums:
http://forum.developers.facebook.net/viewtopic.php?id=93086
People say that the only way to create test users for native apps is to create proper fake accounts on FB, which is against FB terms and conditions. Is this really my only option ? I can't believe the FB devs cannot support test accounts for native apps.
Anyone know of any legitimate way to create native app test users ?
At the top of the developer documentation for test users, a GUI for maintaining test users is also mentioned.
In addition to the Graph API functionality described below for managing test users programmatically, there is also a simple GUI in the Developer App, available on your app's Roles page as shown in the screenshots below. It exposes all of the API functions described in this document.
For some reason I don't understand, the Graph API calls aren't available if your app has an 'App Type' (under Settings -> Advanced) of Native/Desktop. But you can use the GUI instead.
You can create a new facebook test user for your app by calling the below function.
Please user your APP ID (go to this link to get your App's ID:https://developers.facebook.com/apps)
Please go to your app on facebook and get the App secret ID
-(void)createNewUser{
NSString *appID=#"Past your App ID"
NSString *appSecret=#"Past your App secret"
NSDictionary *params = #{
#"true": #"installed",
#"access_token": #"appID|appSecret",
};
NSString *path = [NSString stringWithFormat:#"/appID/accounts/test-users"];
/* make the API call */
[FBRequestConnection startWithGraphPath:path
parameters:params
HTTPMethod:#"POST"
completionHandler:^(
FBRequestConnection *connection,
id result,
NSError *error
)
{
if (result && !error)
{
NSLog(#"Test-User created successfully: %#", result);
}
else
{
NSLog(#"Error creating test-user: %#", error);
NSLog(#"Result Error: %#", result);
}
}];
}
Please make sure you don't reveal your App secret ID to anyone and never include it in your app hardcoded like that.
Hope it helps
The following works for me using the Facebook iOS SDK v4.1.0:
// Listed as "App ID": https://developers.facebook.com/apps/
NSString *facebookAppId = #"1234_REPLACE_ME_5678";
// Listed as "App Token": https://developers.facebook.com/tools/accesstoken/
NSString *facebookAppToken = #"ABCD_REPLACE_ME_1234";
FBSDKGraphRequest *request = [[FBSDKGraphRequest alloc]
initWithGraphPath:[NSString stringWithFormat:#"/%#/accounts/test-users",
facebookAppId]
parameters:#{#"installed" : #"true"}
tokenString:facebookAppToken
version:#"v2.3"
HTTPMethod:#"POST"];
[request startWithCompletionHandler:^(FBSDKGraphRequestConnection *connection,
NSDictionary *testUser,
NSError *facebookError) {
if (facebookError) {
NSLog(#"Error creating test user: %#", facebookError);
} else {
NSLog(#"access_token=%#", testUser[#"access_token"]);
NSLog(#"email=%#", testUser[#"email"]);
NSLog(#"user_id=%#", testUser[#"id"]);
NSLog(#"login_url=%#", testUser[#"login_url"]);
NSLog(#"password=%#", testUser[#"password"]);
}
}];
When successful, the output is then something like this:
access_token=CACQSQXq3KKYeahRightYouDontGetToLookAtMyAccessTokenspBkJJK16FHUPBTCKIauNO8wZDZD
email=aqhndli_huisen_1430877783#tfbnw.net
user_id=1384478845215781
login_url=https://developers.facebook.com/checkpoint/test-user-login/1384478845215781/
password=1644747383820
Swift4 version.
import FBSDKCoreKit
// App Token can be found here: https://developers.facebook.com/tools/accesstoken/
let appToken = "--EDITED--"
let appID = "--EDITED--"
let parameters = ["access_token": appID + "|" + appToken, "name": "Integration Test"]
let path = "/\(appID)/accounts/test-users"
guard let request = FBSDKGraphRequest(graphPath: path, parameters: parameters, httpMethod: "POST") else {
fatalError()
}
request.start { _, response, error in
if let error = error {
// Handle error.
return
}
if let response = response as? [AnyHashable: Any],
let userID = response["id"] as? String,
let token = response["access_token"] as? String,
let email = response["email"] as? String {
// Handle success response.
} else {
// Handle unexpected response.
}
}
You can use Facebook's Test User API https://developers.facebook.com/docs/test_users/ to create test users, just be sure to use the same App id.
Given a APP_ID and APP_SECRET, the following gives us an APP_ACCESS_TOKEN
curl -vv 'https://graph.facebook.com/oauth/access_token?client_id=APP_ID&client_secret=APP_SECRET&grant_type=client_credentials'
then we can use the token to create test users:
curl 'https://graph.facebook.com/APP_ID/accounts/test-users?installed=true&name=NEW_USER_NAME&locale=en_US&permissions=read_stream&method=post&access_token=APP_ACCESS_TOKEN
in the response body to this command will the new users' email and password.