Cordova/Ionic IAP not always works - ios

I have made my Cordova/Ionic app with in app purchases. During test period all works fine, published it live in Google Play and App Store all seems to be fine. Get lots of purchases, tested it myself on different devices and all seems ok. But then after a week the first email came in they purchases an IAP and it was not unlocked (iOS, have any issues heared from Android users). Tow weeks later a second... now I have like 15 people where is does not work and I have seen prove that they have purchased it.
I am using this IAP plugin:
https://github.com/AlexDisler/cordova-plugin-inapppurchase
Before that one I used another IAP plugin, but because of the same problem I started to use the first one:
https://github.com/j3k0/cordova-plugin-purchase
I also made a issue post on github but still no answer:
As I am not allowed to post more links atm, it is issue 113.
The code I use works in 99.9%:
loadProducts = function () {
$ionicLoading.show({ template: spinner + 'Loading...' });
inAppPurchase
.getProducts(productIds)
.then(function (products) {
$ionicLoading.hide();
// Load all products by ID
products.forEach(function (product) {
// Add extra field to the product object
product.owned = false;
switch (product.productId) {
case ncProductProVer:
if (GlobalVars.vars.fullVersion == true) {
product.owned = true;
}
break;
case ncProductNoAds:
if (GlobalVars.vars.noAds == true) {
product.owned = true;
}
break;
}
});
//alert(products[0].productId + ' ' + products[1].productId);
$scope.products = products;
})
.catch(function (err) {
$ionicLoading.hide();
//$cordovaDialogs.alert('Error loading products. Details:\n- Code: ' + err.code + '\n- Message: ' + err.message, 'Error', 'OK')
$scope.CodeError = true;
});
};
// BUY a product
$scope.buy = function (productId) {
$ionicLoading.show({ template: spinner + 'Buying...' });
inAppPurchase
.buy(productId)
.then(function (data) {
$ionicLoading.hide();
enableIAP(productId, true);
})
.then(function () {
$ionicLoading.hide();
loadProducts();
})
.catch(function (err) {
$ionicLoading.hide();
errHandling(err.code, productId);
});
};
// RESTORE in app purchases
$scope.restore = function () {
$ionicLoading.show({ template: spinner + 'Restoring purchases...' });
inAppPurchase
.restorePurchases()
.then(function (purchases) {
$ionicLoading.hide();
purchases.forEach(function (purchase) {
// Unlock the relevant feature based on this product id, if any
// Store the purchase in localStorage
enableIAP(purchase.productId, true);
});
// Reload the productlist
loadProducts();
})
.catch(function (err) {
$ionicLoading.hide();
});
};
I have tried many different things already, but cant figure out what the problem can be or how I can see what goes wrong as the app runs on a device where I don't have access to :).
The only thing I have is that people say the 'Buying...' popup keeps on loading till they close the app and run it again. So the problem must me in the .buy() function and does not give a error back but otherwise it will be in the .catch().
Things I can think of is:
- Apple does not always sends the same string back
- The string send back from Apple contains chars that are not correctly handled by the IAP code
To make things more odd, I managed to get in contact with somebody where it did not work. So I started testing some things as I thought maybe it could be device settings. So I had the iPhone and signed in with my iTunes account, downloaded the app and restored the IAP and it worked. Did the same thing, sign in with their iTunes account, did not work. That person also had an iPad mini, where it all worked as expected, the extra item in the app where unlocked as they should.
Anybody seen this strange behaviour? Been busy many weeks/months already to figure out what it could be.

Related

Flutter ios appstore validateReceipt on non-consumable in-app purchase

I seem to be stuck on this. Trying to validate the receipt (server side) on an in-app purchase on IOS (haven't tried with android, yet.)
I'm using the official in_app_purchase pub package.
This is the setup to initialize the purchase:
Future<bool> initiatePurchase() async {
...
(verify store is available)
..
print ("==> Store available, initiating purchase");
final PurchaseParam purchaseParam =
PurchaseParam(productDetails: _productDetails![0]);
await InAppPurchase.instance.buyNonConsumable(purchaseParam: purchaseParam);
return true;
}
Here's my verify purchase call:
Future<bool> _verifyPurchase(PurchaseDetails purchaseDetails) async {
PurchaseVerifRest purchaseRest = PurchaseVerifRest();
Map<String,dynamic> rsp = await purchaseRest.verifyPurchase(
{
"source": purchaseDetails.verificationData.source,
"vfdata": purchaseDetails.verificationData.serverVerificationData
});
// bundle up the source and verificationData in a map and send to rest
// call
return rsp['status'] == 200;
}
On the server side, the code looks like this (NodeJS/express app)
// (in router.post() call - 'purchaseData' is the map sent in the above code,
// the 'vfdata' member is the 'serverVerificationData'
//. in the 'purchaseDetails' object)
if (purchaseData.source == ('app_store')) {
const IOS_SHARED_SECRET = process.env...;
let postData = {
'receipt-data': purchaseData['vfdata'],
'password': IOS_SHARED_SECRET
};
try {
let verif_rsp = await execPost(postData);
retStatus = verif_rsp.statusCode;
msg = verif_rsp.data;
} catch (e) {
retStatus = e.statusCode;
}
}
What I get back, invariably is
210003 - Receipt could not be authenticated
... even though the purchase seems to go through, whether I validate or not.
Details/questions:
Testing with a sandbox account.
This is for a 'non-consumable' product purchase.
I'm assuming that purchaseDetails.verificationData.serverVerificationData is the payload containing the receipt to send to Apple for verification. Is this not correct? Is there another step I need to do to get the receipt data?
I've read in other posts that the verification step is only for recurring subscriptions and not for other types of products. Is this correct? I don't see anything in Apple's docs to indicate this.
Any thoughts appreciated.

cordova-plugin-purchase on iOS: order() does not work without uninstalling the app first

We are developing an application that uses Ionic (Angular) and Capacitor together with cordova-plugin-purchase to offer subscriptions (monthly and yearly). Purchases work perfectly on Android. However, on iOS, while the first purchase works fine, after that purchase expires (via cancelling the subscription, since it is actually automatically renewing) and the user purchases any product again (now on valid status since it expired already), calling the order() function does not do anything. The product status remains valid, no logs are written in the JavaScript console, nor Xcode. No errors, too. We have verified that our verification servers are returning correct responses. However, when we uninstall the app and install it from Xcode again, purchases start working again, and calling the order() function does show the purchase dialog again, and things work as expected. Until the subscription expires and they try to purchase again.
However, if the product is not yet expired, and the user tries to change subscription by subscribing to another subscription type, calling the order() function works as expected. It is only when a subscription expires, and then it starts to not work until the application is deleted/uninstalled and then installed again.
We implement the purchase using pretty standard code--we tried as minimal as possible to make it simple.
Initialization:
this.store.validator = 'https://xxxxxxx/api/app/purchased';
this.store.register([
{
type: this.store.PAID_SUBSCRIPTION,
id: this.YEARLY_SUBSCRIPTION
},
{
type: this.store.PAID_SUBSCRIPTION,
id: this.MONTHLY_SUBSCRIPTION
}
]);
this.subscriptions.add(
this.status.userData.subscribe(ud => {
this.user = ud;
this.store.applicationUsername = ud.id.toString(10);
})
);
this.store.when(this.YEARLY_SUBSCRIPTION)
.cancelled(this.purchaseCancelled)
.updated(this.purchaseYearlyUpdated)
.approved(this.purchaseApproved)
.verified(this.purchaseVerified)
.owned(this.purchaseOwned);
this.store.when(this.MONTHLY_SUBSCRIPTION)
.cancelled(this.purchaseCancelled)
.updated(this.purchaseMonthlyUpdated)
.approved(this.purchaseApproved)
.verified(this.purchaseVerified)
.owned(this.purchaseOwned);
this.store.error(this.handleError);
this.store.autoFinishTransactions = true;
this.store.refresh();
Other functions:
purchaseCancelled = (p: IAPProduct) => {
console.log(`${ p.id } cancelled`);
}
purchaseYearlyUpdated = (p: IAPProduct) => {
console.log(`${ p.id } updated`);
this.yearlyProduct = p;
this.changeDetectorRef.detectChanges();
}
purchaseMonthlyUpdated = (p: IAPProduct) => {
console.log(`${ p.id } updated`);
this.monthlyProduct = p;
this.changeDetectorRef.detectChanges();
}
purchaseApproved = (p: IAPProduct) => {
console.log(`${ p.id } approved`);
p.verify();
}
purchaseVerified = (p: IAPProduct) => {
console.log(`${ p.id } verified`);
p.finish();
}
purchaseOwned = (p: IAPProduct) => {
console.log(`${ p.id } owned`);
}
handleError = (error) => {
console.log('Error event:', error);
}
purchase(id: string) {
this.store.order(id);
}
ngOnDestroy() {
this.subscriptions.unsubscribe();
this.store.off(this.purchaseCancelled);
this.store.off(this.purchaseYearlyUpdated);
this.store.off(this.purchaseMonthlyUpdated);
this.store.off(this.purchaseApproved);
this.store.off(this.purchaseVerified);
this.store.off(this.purchaseOwned);
this.store.off(this.handleError);
}
Steps to reproduce:
Install the app from Xcode.
Purchase a product (user clicks the purchase button, that button triggers store.order)
Exit app
Cancel subscription so it expires
Subscription expires
Open app, and confirm that subscription is indeed expired (status goes from approved to valid)
Try to purchase subscription again by clicking the purchase button (any subscription)
Nothing happens. No logs. No errors. Product status remains valid.
Closing the app and then running again does not help.
Uninstall app
Install app again from Xcode
Purchase works again
I can't see anything wrong with the code, and since it's working perfectly fine on Android, we're guessing that it might be something wrong with the phone or maybe the cordova-plugin-purchase plugin.
I would really appreciate any suggestions, ideas, opinions, anything. Thank you very much in advance. I'm getting quite desperate.

Potential causes of Ionic function not working in Ionic View, but working on web

I am building a food delivery app using Ionic. And I am having problems getting the app to work on mobile for the address creation step. After creating an account the user must create a delivery address, at which point the app figures out what delivery location to use.
Address creation works in Chrome (ionic serve) and in iOS simulator (ionic run ios -l -c -s).
However, once I've uploaded the app to my Ionic View iOS app for testing, it gets stuck at the Address creation step.
But at the address creation step, the Ionic loading wheel starts but it doesn't go away and there is no state transition to the menu.
Here is the implementation in the controller.
Address.create($scope.newAddress, $scope.user)
.then(function(response) { // never gets a response back in Ionic View
console.log("address created");
user.save(null,
{ success: function(user) {
// success callback
}, error: function(error) {
// throw error
}
});
}, function(error) {
// throw error
});
The Address.create() method I have implemented is fairly lengthy:
...
.factory('Address', ['$http', '$q', 'PARSE_HEADERS'
function ($http, $q, PARSE_HEADERS) {
return {
create: function(data, userID) {
var deferred = $q.defer();
var zipArray = ['1111','22222','33333'];
var inZone = false;
var restaurantCoords = {
latitude: 11.11111, longitude: 22.22222
};
for (var i=0, bLen=zipBrooklyn.length; i<bLen; i++) {
if(data.zipCode==zipArray[i]) {
inZone = true;
}
}
if (inZone == true ) { // valid zip
function onSuccess(coords) {
var limit = 3041.66;
var meters = getDistance(coords, restaurantCoords);
if (meters < limit) {
$http.post('https://api.parse.com/1/classes/Address', data, {
headers: PARSE_HEADERS
})
.success(function(addressData) {
deferred.resolve(addressData);
})
.error(function(error, addressData) {
deferred.reject(error);
});
}
function onError() {
deferred.reject("Unable to Geocode the coordinates");
}
// GET COORDS
navigator.geocoder.geocodeString(onSuccess, onError, data.address1 + ',' + data.zipCode);
}
}
return deferred.promise;
}]);
I've stripped out all of the code that I believe was working.
So a valid answer for this question could take multiple forms:
I'd accept an answer giving a decent way to debug apps IN Ionic View.
Or, if someone could provide an answer as to why it might be working in the browser and in iOS Simulator, but not iOS itself, that would be appreciated even more.
Ionic view doesn't support all the plugins yet. please take a look at this link for the list of supported plugins.
Device is always better (First Option). If you have a ios device and apple developer account. You can create and configure the required certificate with the device id and run the app using 'ionic run ios'. Second option is iOS simulator. You can use the simulator for your whole app, though few tasks would need a device.
Even if you use the simulator for the whole development, it is always advisable to test in the device before launcing the app.

Error in FB.login() phonegap facebook plugin

When i run fb.login() in an ios 6 device using the account setted in Settings/facebook i get the following
operation couldn't be completed (com.facebook.sdk error 2)
in ios 5 the app works perfectly, the problem is the native login
help please ! this is my code
this is the facebook init
this.appId = appId;
var me = this;
document.addEventListener('deviceready', function() {
try {
FB.init({ appId: "294003447379425", status: true, cookie: true, nativeInterface: CDV.FB, useCachedDialogs: false });
} catch (e) {
console.log(e);
}
}, false);
if (!this.appId) {
Ext.Logger.error('No Facebook Application ID set.');
return;
}
var me = this;
me.hasCheckedStatus = false;
FB.Event.subscribe('auth.logout', function() {
// This event can be fired as soon as the page loads which may cause undesired behaviour, so we wait
// until after we've specifically checked the login status.
if (me.hasCheckedStatus) {
me.fireEvent('logout');
}
});
// Get the user login status from Facebook.
FB.getLoginStatus(function(response) {
me.fireEvent('loginStatus');
clearTimeout(me.fbLoginTimeout);
me.hasCheckedStatus = true;
if (response.status == 'connected') {
me.fireEvent('connected');
Ext.Viewport.add(Ext.create('CinePass.view.Main'));
} else {
me.fireEvent('unauthorized');
console.log('noconectado');
Ext.Viewport.add(Ext.create('CinePass.view.LoggedOut'));
}
});
// We set a timeout in case there is no response from the Facebook `init` method. This often happens if the
// Facebook application is incorrectly configured (for example if the browser URL does not match the one
// configured on the Facebook app.)
me.fbLoginTimeout = setTimeout(function() {
me.fireEvent('loginStatus');
me.fireEvent('exception', {
type: 'timeout',
msg: 'The request to Facebook timed out.'
});
Ext.Msg.alert('CinePass', 'Existe un problema con Facebook, se iniciará la aplicación sin conexión a Facebook.', Ext.emptyFn);
Ext.Viewport.add(Ext.create('CinePass.view.Main'));
}, me.fbTimeout);
this is the login
FB.login(
function(response) {
if (response.session) {
alert(response.session);
} else {
alert(response.session);
}
},
{ scope: "email,user_about_me,user_activities,user_birthday,user_hometown,user_interests,user_likes,user_location,friends_interests" }
);
Having the same problem and took me few days to figure this out, there are a number of reasons:
1) The first time you launch your app, if you deny the "Do you allow this app to use your facebook permission", you'll get this error, what you need to do is, go to Setting > General > Facebook > turn on your app OR go to Setting > General > Reset > Reset Location & Privacy (this will reset and ask you the "Do you allow this app to use your facebook permission" again for all app.
2) Slow/No Internet can caused the error
3) Create your certificate again, and go to build.phonegap.com, create a new key for IOS and change the IOS key (This works for me, I have no idea how it works but it does, I notice a significant app file size increase after i use a new IOS Key)
4) If your app is in sand box mode, and your IOS current Facebook account is not an app tester you'll get the error too. I just disable the sand box mode and it works, make sure your Facebook App Setting has the correct bundle ID, if your app is still under developement, put 0 to app store ID.

Go to App Store Reviews in iOS 6

It appears that using the itms-apps//.... URL scheme doesn't work in iOS 6 with the new App Store to show the product reviews area. Now I'm using the code below, but it just shows you the product. How do you get to the review area to ask for a review and take the user to the right tab of the product displayed?
void DoReview()
{
var spp = new StoreProductParameters(appId);
var productViewController = new SKStoreProductViewController();
// must set the Finished handler before displaying the view controller
productViewController.Finished += (sender2, err) => {
// Apple's docs says to use this method to close the view controller
this.navigationController.DismissViewController(true, null);
MySettings.AskedForReview = true;
};
productViewController.LoadProduct(spp, (ok, err) => { // ASYNC !!!
if (ok)
{
this.navigationController.PresentViewController(productViewController, true, null);
}
else
{
Console.WriteLine(" failed ");
if (err != null)
Console.WriteLine(" with error " + err);
}
});
}
Hello you could try iRate from Nick Lockwood and see if that fits your needs, you can find the MonoTouch bindings of iRate here.
btw it uses the following URL to open AppStore in review mode:
itms-apps://ax.itunes.apple.com/WebObjects/MZStore.woa/wa/viewContentsUserReviews?type=Purple+Software&id= your-AppID-here
Alex

Resources