Need help utilizing Beeblex validating in-app purchases with xCode - xcode

Has anyone had success using Beeblex to validate in-app purchases? I have an app that is already live in the app-store with in-app purchases enabled and working. In light of the recent hacking news concerning in-app purchases I am attempting to update it to use Beeblex to validate purchases. The problem is that Beeblex always returns a result saying that Apple claims the purchase to be invalid. I am hoping someone on here is successfully using Beeblex and can post a working example. I am following their advice to let the purchase go completely through, then validate it. I basically just cut and pasted their code right off their website, so not sure what I could do differently.
- (void) validatePurchaseOrRestore
{
//The transaction has been reported as complete for a new purchase of the upgrade. I now make use of Beeblex
//to verify the receipt to ensure the purchase is legit.
if (![BBXIAPTransaction canValidateTransactions])
{
transactionResult = 4;
return; // There is no connectivity to reach the server.
// You should try the validation at a later date.
}
BBXIAPTransaction *bbxTransaction = [[BBXIAPTransaction alloc] initWithTransaction:transactionCopy];
bbxTransaction.useSandbox;
[bbxTransaction validateWithCompletionBlock:^(NSError *error)
{
if (bbxTransaction.transactionVerified)
{
if (bbxTransaction.transactionIsDuplicate)
{
// The transaction is valid, but duplicate - it has already been
// sent to Beeblex in the past.
transactionResult = 1;
}
else
{
// The transaction has been successfully validated
// and is unique.
NSLog(#"Transaction data: %#", bbxTransaction.validatedTransactionData);
transactionResult = 0;
}
}
else
{
// Check whether this is a validation error, or if something
// went wrong with Beeblex.
if (bbxTransaction.hasServerError)
{
// The error was not caused by a problem with the data, but is
// most likely due to some transient networking issues.
transactionResult = 4;
}
else
{
// The transaction supplied to the validation service was not valid according to Apple.
transactionResult = 3;
purchaseDidFail = TRUE;
}
}
}];
}

Related

SquarePointOfSaleSDK failing on callback in Swift Xcode 13.3

I have a tiny Square iOS POS testing app to get the setup working properly but alas, no such luck - it charges $1.00 and the card is correctly processed, but the callback does not happen. The Square app just drops control and gives the screen back to my calling view controller - it does not return to the appDelegate. I have sent this project to Square, hoping for help, but they refuse, saying it is an implementation problem. However it definitely is a problem with Square's documentation, not implementation, as I have set up everything perfectly including the info file, the URL, the appID etc as defined. I am looking for help.
API call follows:
//------ API to Square right here ******-----------------
let callbackURL = URL(string: "POSSDKTest2://")!
var money: SCCMoney?
do {
// Specify the amount of money to charge.
let todaysFeePennies = Int(TodaysFee) * 100
money = try SCCMoney(amountCents: todaysFeePennies, currencyCode: "CAD")
} catch {
print("money conversion failed.")
}
//SCCAPIRequest.setApplicationID(masterInfo.SquareAppID)
SCCAPIRequest.setApplicationID("sq0idp-TbgQGqSrC84qkfcXSTntNg")
var request: SCCAPIRequest?
do
{
let LoginID = "TestPatient"
squareTransDone = false
print("FVC Initiating payment api request")
print("callback: \(callbackURL)")
print("money: \(money!)")
print("LoginID: TestPatient")
print("FVC Initiating payment api request")
request =
try SCCAPIRequest(
callbackURL: callbackURL,
amount: money!,
userInfoString: LoginID,
locationID: "",
notes: "Testing SDK",
customerID: nil, // used by Square, not us
supportedTenderTypes: .all,
clearsDefaultFees: false,
returnsAutomaticallyAfterPayment: false,
//.true makes no difference!
disablesKeyedInCardEntry: false,
skipsReceipt: false)
} catch {
print("SCCAPIRequest croaked.")
}
// Open Point of Sale to complete the payment.
var success = false
do {
try SCCAPIConnection.perform(request!)
success = true
print("api success!")//this actually shows in the log
} catch let error as NSError {
print("Error detected: \(error.localizedDescription)")
}//end API
}//------------------------------------
The calling view controller has a screen with a button saying "Post $1.00 payment" which comes to this code. The log shows "api success" and no error message.
Image of app's info.plist attached showing setup for Square.
info.plist screenshot
Image of Credentials from Square Developer page attached.
Credentials page

Cloudkit: " error saving record WRITE operation not permitted"

I'm trying to save a record CloudKit but I'm getting the following error from cloudkit:
error saving record este es error: Error saving record <CKRecordID: 0x7fef15b5d2a0; 2:(_defaultZone:__defaultOwner__)> to server: WRITE operation not permitted
Here is how I'm trying to save the record:
[publicDatabase saveRecord:recordContent completionHandler:^(CKRecord *record, NSError *error){
if (!error)
{
NSLog(#"saved!!!");
}
else
{
if ([[error.userInfo valueForKey:#"ErrorDescription"] isEqualToString:#"record to insert already exists"])
{
NSLog(#"record already exist %#",[error.userInfo valueForKey:#"ErrorDescription"]);
}
NSLog(#"error saving record : %#",error.localizedDescription);
}
}];
But before had I check if cloudkit is available:
[myContainer accountStatusWithCompletionHandler:^(CKAccountStatus accountStatus, NSError *error)
{
NSLog(#" no error but status %ld",accountStatus);
if (((accountStatus == 3) || (accountStatus == 2)) && (!error))
{
NSLog(#" no error but status %ld",accountStatus);
// typedef NS_ENUM(NSInteger, CKAccountStatus) {
// /* An error occurred when getting the account status, consult the corresponding NSError */
// CKAccountStatusCouldNotDetermine = 0,
// /* The iCloud account credentials are available for this application */
// CKAccountStatusAvailable = 1,
// /* Parental Controls / Device Management has denied access to iCloud account credentials */
// CKAccountStatusRestricted = 2,
// /* No iCloud account is logged in on this device */
// CKAccountStatusNoAccount = 3,
//
// }
}
if (error)
{
NSLog(#" accountStatus error %#",error);
}
} ];
Where I'm getting status 1, meaning CKAccountStatusAvailable.
Any of you knows why this is happening it has been working fine until the last record or any of you knows a work around this?
I'll really appreciate your help.
You need to set permission to allow a user to write (or delete) a record created by someone else. You do that in the Development Environment, under Schema, Record Types, select the specific record, then over on the right there is a drop down menu labelled Security. Grant to the Role ' Authenticated' the right to Read and Write. Then deploy to Production.
This was moved in the latest CloudKit, took me a while to track it down.
Beware, this isn't instant, it takes a while for these changes to propagate after saving them. Come back later and refresh the page to see if they've been applied.
If you are still getting this error after setting these permissions and letting them propagate then it's likely that your iCloud Login in Simulator is messed up. Logging out of iCloud and logging in again fixed this for me.
I spent 2 days for "Permission failure (10/2007)". All permissions and security roles was set as in William.T picture. Relogin into iCloud doesn't got any results.
The snag was in one small setting (not documented by Apple) - it was switcher "iCloud Drive" which located in
Settings/[your account]/iCloud/iCloud Drive
on my device. Apple's documentation is terrible! no one will return me 2 days spent.
Hope this helps someone with the same problem.
In my case i forgot to do a basic check: make sure you're logged in with your Apple ID and make sure the iCloud checkmark for your application is turned on.
This can happen especially using Simulators.

Google+ insert moment using google-api-dotnet-client

I am trying to write an activity in Google+ using the dotnet-client. The issue is that I can't seem to get the configuration of my client app correctly. According to the Google+ Sign-In configuration and this SO question we need to add the requestvisibleactions parameter. I did that but it did not work. I am using the scope https://www.googleapis.com/auth/plus.login and I even added the scope https://www.googleapis.com/auth/plus.moments.write but the insert still did not work.
This is what my request url looks like:
https://accounts.google.com/ServiceLogin?service=lso&passive=1209600&continue=https://accounts.google.com/o/oauth2/auth?scope%3Dhttps://www.googleapis.com/auth/plus.login%2Bhttps://www.googleapis.com/auth/plus.moments.write%26response_type%3Dcode%26redirect_uri%3Dhttp://localhost/%26state%3D%26requestvisibleactions%3Dhttp://schemas.google.com/AddActivity%26client_id%3D000.apps.googleusercontent.com%26request_visible_actions%3Dhttp://schemas.google.com/AddActivity%26hl%3Den%26from_login%3D1%26as%3D-1fbe06f1c6120f4d&ltmpl=popup&shdf=Cm4LEhF0aGlyZFBhcnR5TG9nb1VybBoADAsSFXRoaXJkUGFydHlEaXNwbGF5TmFtZRoHQ2hpa3V0bwwLEgZkb21haW4aB0NoaWt1dG8MCxIVdGhpcmRQYXJ0eURpc3BsYXlUeXBlGgdERUZBVUxUDBIDbHNvIhTeWybcoJ9pXSeN2t-k8A4SUbfhsygBMhQivAmfNSs_LkjXXZ7bPxilXgjMsQ&scc=1
As you can see from there that there is a request_visible_actions and I even added one that has no underscore in case I got the parameter wrong (requestvisibleactions).
Let me say that my app is being authenticated successfully by the API. I can get the user's profile after being authenticated and it is on the "insert moment" part that my app fails. My insert code:
var body = new Moment();
var target = new ItemScope();
target.Id = referenceId;
target.Image = image;
target.Type = "http://schemas.google.com/AddActivity";
target.Description = description;
target.Name = caption;
body.Target = target;
body.Type = "http://schemas.google.com/AddActivity";
var insert =
new MomentsResource.InsertRequest(
// this is a valid service instance as I am using this to query the user's profile
_plusService,
body,
id,
MomentsResource.Collection.Vault);
Moment result = null;
try
{
result = insert.Fetch();
}
catch (ThreadAbortException)
{
// User was not yet authenticated and is being forwarded to the authorization page.
throw;
}
catch (Google.GoogleApiRequestException requestEx)
{
// here I get a 401 Unauthorized error
}
catch (Exception ex)
{
} `
For the OAuth flow, there are two issues with your request:
request_visible_actions is what is passed to the OAuth v2 server (don't pass requestvisibleactions)
plus.moments.write is a deprecated scope, you only need to pass in plus.login
Make sure your project references the latest version of the Google+ .NET client library from here:
https://developers.google.com/resources/api-libraries/download/stable/plus/v1/csharp
I have created a project on GitHub showing a full server-side flow here:
https://github.com/gguuss/gplus_csharp_ssflow
As Brettj said, you should be using the Google+ Sign-in Button as demonstrated in the latest Google+ samples from here:
https://github.com/googleplus/gplus-quickstart-csharp
First, ensure you are requesting all of the activity types you're writing. You will know this is working because the authorization dialog will show "Make your app activity available via Google, visible to you and: [...]" below the text that starts with "This app would like to". I know you checked this but I'm 90% sure this is why you are getting the 401 error code. The following markup shows how to render the Google+ Sign-In button requesting access to Add activities.
<div id="gConnect">
<button class="g-signin"
data-scope="https://www.googleapis.com/auth/plus.login"
data-requestvisibleactions="http://schemas.google.com/AddActivity"
data-clientId="YOUR_CLIENT_ID"
data-accesstype="offline"
data-callback="onSignInCallback"
data-theme="dark"
data-cookiepolicy="single_host_origin">
</button>
Assuming you have a PlusService object with the correct activity type set in data-requestvisibleactions, the following code, which you should be able to copy/paste to see it work, concisely demonstrates writing moments using the .NET client and has been tested to work:
Moment body = new Moment();
ItemScope target = new ItemScope();
target.Id = "replacewithuniqueforaddtarget";
target.Image = "http://www.google.com/s2/static/images/GoogleyEyes.png";
target.Type = "";
target.Description = "The description for the activity";
target.Name = "An example of add activity";
body.Target = target;
body.Type = "http://schemas.google.com/AddActivity";
MomentsResource.InsertRequest insert =
new MomentsResource.InsertRequest(
_plusService,
body,
"me",
MomentsResource.Collection.Vault);
Moment wrote = insert.Fetch();
Note, I'm including Google.Apis.Plus.v1.Data for convenience.
Ah it's that simple! Maybe not? I am answering my own question and consequently accept it as the answer (after a few days of course) so others having the same issue may be guided. But I will definitely up-vote Gus' answer for it led me to the fix for my code.
So according to #class answer written above and as explained on his blog the key to successfully creating a moment is adding the request_visible_actions parameter. I did that but my request still failed and it is because I was missing an important thing. You need to add one more parameter and that is the access_type and it should be set to offline. The OAuth request, at a minimum, should look like: https://accounts.google.com/o/oauth2/auth?scope=https://www.googleapis.com/auth/plus.login&response_type=code&redirect_uri=http://localhost/&request_visible_actions=http://schemas.google.com/AddActivity&access_type=offline.
For the complete and correct client code you can get Gus' example here or download the entire dotnet client library including the source and sample and add what I added below. The most important thing that you should remember is modifying your AuthorizationServerDescription for the Google API. Here's my version of the authenticator:
public static OAuth2Authenticator<WebServerClient> CreateAuthenticator(
string clientId, string clientSecret)
{
if (string.IsNullOrWhiteSpace(clientId))
throw new ArgumentException("clientId cannot be empty");
if (string.IsNullOrWhiteSpace(clientSecret))
throw new ArgumentException("clientSecret cannot be empty");
var description = GoogleAuthenticationServer.Description;
var uri = description.AuthorizationEndpoint.AbsoluteUri;
// This is the one that has been documented on Gus' blog site
// and over at Google's (https://developers.google.com/+/web/signin/)
// This is not in the dotnetclient sample by the way
// and you need to understand how OAuth and DNOA works.
// I had this already, see my original post,
// I thought it will make my day.
if (uri.IndexOf("request_visible_actions") < 1)
{
var param = (uri.IndexOf('?') > 0) ? "&" : "?";
description.AuthorizationEndpoint = new Uri(
uri + param +
"request_visible_actions=http://schemas.google.com/AddActivity");
}
// This is what I have been missing!
// They forgot to tell us about this or did I just miss this somewhere?
uri = description.AuthorizationEndpoint.AbsoluteUri;
if (uri.IndexOf("offline") < 1)
{
var param = (uri.IndexOf('?') > 0) ? "&" : "?";
description.AuthorizationEndpoint =
new Uri(uri + param + "access_type=offline");
}
// Register the authenticator.
var provider = new WebServerClient(description)
{
ClientIdentifier = clientId,
ClientSecret = clientSecret,
};
var authenticator =
new OAuth2Authenticator<WebServerClient>(provider, GetAuthorization)
{ NoCaching = true };
return authenticator;
}
Without the access_type=offline my code never worked and it will never work. Now I wonder why? It would be good to have some explanation.

iOS 6 contacts access alert never showed on debug

My app on the appstore is accessing the iPhone contacts, after the users downloaded it on iOS 6 it can't access the iPhone contacts while its working fine on iOS 5
the problem is the new privacy settings apple has put in iOS 6 .. so after searching i found out that i have to do the following in my code to be able to access the user contacts:
//in order to test addressbook availability we have to attempt to create an addressbook instance using ABAddressBookCreateWithOptions
#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 60000
// Request authorization to Address Book
ABAddressBookRef addressBookRef = ABAddressBookCreateWithOptions(NULL, NULL);
if (ABAddressBookGetAuthorizationStatus() == kABAuthorizationStatusNotDetermined) {
ABAddressBookRequestAccessWithCompletion(addressBookRef,
^(bool granted, CFErrorRef error) {
if (granted)
[self loadContacts];
});
} else if (ABAddressBookGetAuthorizationStatus() == kABAuthorizationStatusAuthorized) {
// The user has previously given access, add the contact
[self loadContact];
} else {
}
#endif //end iOS6+
//ABAddressBookCreateWithOptions not available or succeeded. return YES;
[self loadContacts];
My problem now is while debugging on the device, the alert is not showing, i don't know why ?
I know that the above code should work fine, but only when the app is submitted to the appstore but i want to test that in debug mode before submission ?
Any advice ?
Appreciate your support.
Thanks.
I have managed to get it resolved
Here is the new code after a slight modification:
#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 60000
__block MyClassType *controller = self;
// Request authorization to Address Book
ABAddressBookRef addressBookRef = ABAddressBookCreateWithOptions(NULL, NULL);
if (ABAddressBookGetAuthorizationStatus() == kABAuthorizationStatusNotDetermined) {
ABAddressBookRequestAccessWithCompletion(addressBookRef,
^(bool granted, CFErrorRef error) {
if (granted)
[controller loadContacts];
});
} else if (ABAddressBookGetAuthorizationStatus() == kABAuthorizationStatusAuthorized) {
// The user has previously given access, add the contact
[self loadContacts];
} else {
}
#else
[self loadContacts];
#endif
The key to be able to test it is to Reset the Privacy and location settings from Settings>>General>>Reset>>Reset Location & Privacy
It worked fine with me.
Resetting Location & Privacy doesn't work for me.
My authorization status is always kABAuthorizationStatusAuthorized, regardless of whether I clear simulator settings and then reset Location and Privacy.

Saving Data Locally and Remotely (Syncing)

When data is entered, it ultimately needs to be saved remotely on a server. I do want the app to work if there is no data connection at the time also, so I need to save everything locally on the phone too. The app can then sync with the server when it gets a connection.
This brings up a little issue. I'm used to saving everything on the server and then getting the records back with id's generated from the server for them. If there is no connection, the app will save locally to the phone but not the server. When syncing with the server, I don't see a way for the phone to know when a record comes back which locally record it's associated with. There isn't enough unique data to figure this out.
What is the best way to handle this?
One way I've been thinking is to change the id of the records to a GUID and let the phone set the id. This way, all records will have an id locally, and when saving to the server, it should still be a unique id.
I'd like to know what other people have been doing, and what works and what doesn't from experience.
This is how we done with a first windows phone 7 app finished few days ago with my friend.
It might not be the best solution but 'till additional refactoring it works just fine.
It's an application for a web app like a mint.com called slamarica.
If we have feature like save transaction, we first check if we have connection to internet.
// Check if application is in online or in offline mode
if (NetworkDetector.IsOnline)
{
// Save through REST API
_transactionBl.AddTransaction(_currentTransaction);
}
else
{
// Save to phone database
SaveTransactionToPhone(_currentTransaction);
}
If transaction is successfully saved via REST, it responses with transaction object and than we save it to local database. If REST save failed we save data to local database.
private void OnTransactionSaveCompleted(bool isSuccessful, string message, Transaction savedTransaction)
{
MessageBox.Show(message);
if(isSuccessful)
{
// save new transaction to local database
DatabaseBl.Save(savedTransaction);
// save to observable collection Transactions in MainViewModel
App.ViewModel.Transactions.Add(App.ViewModel.TransactionToTransactionViewModel(savedTransaction));
App.ViewModel.SortTransactionList();
// Go back to Transaction List
NavigationService.GoBack();
}
else
{
// if REST is failed save unsent transaction to Phone database
SaveTransactionToPhone(_currentTransaction);
// save to observable collection Transactions in MainViewModel
App.ViewModel.Transactions.Add(App.ViewModel.TransactionToTransactionViewModel(_currentTransaction));
App.ViewModel.SortTransactionList();
}
}
Every Transaction object has IsInSync property. It is set to false by default until we got confirmation from REST API that it's saved successful on the server.
User has ability to refresh transactions. User can click on a button Refresh to fetch new data from the server. We do the syncing in the background like this:
private void RefreshTransactions(object sender, RoutedEventArgs e)
{
if (NetworkDetector.IsOnline)
{
var notSyncTransactions = DatabaseBl.GetData<Transaction>().Where(x => x.IsInSync == false).ToList();
if(notSyncTransactions.Count > 0)
{
// we must Sync all transactions
_isAllInSync = true;
_transactionSyncCount = notSyncTransactions.Count;
_transactionBl.AddTransactionCompleted += OnSyncTransactionCompleted;
if (_progress == null)
{
_progress = new ProgressIndicator();
}
foreach (var notSyncTransaction in notSyncTransactions)
{
_transactionBl.AddTransaction(notSyncTransaction);
}
_progress.Show();
}
else
{
// just refresh transactions
DoTransactionRefresh();
}
}
else
{
MessageBox.Show(ApplicationStrings.NETWORK_OFFLINE);
}
}
private void DoTransactionRefresh()
{
if (_progress == null)
{
_progress = new ProgressIndicator();
}
// after all data is sent do full reload
App.ViewModel.LoadMore = true;
App.ViewModel.ShowButton = false;
ApplicationBl<Transaction>.GetDataLoadingCompleted += OnTransactionsRefreshCompleted;
ApplicationBl<Transaction>.GetData(0, 10);
_progress.Show();
}
OnTransactionRefreshCompleted we delete all transaction data in local database and get the latest 10 transactions. We don't need all the data, and this way user have synced data. He can always load more data by taping load more at the end of transaction list. It's something similar like those twitter apps.
private void OnTransactionsRefreshCompleted(object entities)
{
if (entities is IList<Transaction>)
{
// save transactions
var transactions = (IList<Transaction>)entities;
DatabaseBl.TruncateTable<Transaction>();
DatabaseBl.Save(transactions);
((MainViewModel) DataContext).Transactions.Clear();
//reset offset
_offset = 1;
//update list with new transactions
App.ViewModel.LoadDataForTransactions(transactions);
App.ViewModel.LoadMore = false;
App.ViewModel.ShowButton = true;
}
if (entities == null)
{
App.ViewModel.ShowButton = false;
App.ViewModel.LoadMore = false;
}
// hide progress
_progress.Hide();
// remove event handler
ApplicationBl<Transaction>.GetDataLoadingCompleted -= OnTransactionsRefreshCompleted;
}
Caveat - I haven't tried this with windows phone development but use of GUID identities is something I usually do when faced with similar situations - eg creating records when I only have a one-way connection to the database - such as via a message bus or queue.
It works fine, albeit with a minor penalty in record sizes, and can also cause less performant indexes. I suggest you just give it a shot.

Resources