I'm having issues trying to get the InAppPurchases to work in my React Native app using Expo. https://docs.expo.io/versions/latest/sdk/in-app-purchases
This is a bare workflow app that I ejected from a standard Expo app.
I made sure to follow the install instructions here carefully: https://docs.expo.io/bare/installing-unimodules/
I did test if unimodules was properly installed:
import { Constants } from "react-native-unimodules";
useEffect(() => {
alert("installed: " + JSON.stringify(Constants.systemFonts));
}, []);
The code above worked.
I'm using react-native-unimodules version 0.11.0.
Here's my code:
useEffect(() => {
(async function init() {
try {
const connect_res = await connectAsync();
alert("connect: " + JSON.stringify(connect_res));
} catch (err) {
alert("general error for connect async: " + err);
}
})();
}, []);
This is in the App.js entrypoint file. It always returns undefined for the connect_res so I assume this is the reason why I couldn't get any of the code to work.
Just below connectAsync() I have the following. This one also doesn't return anything:
setPurchaseListener(({ responseCode, results, errorCode }) => {
if (responseCode === IAPResponseCode.OK) {
results.forEach((purchase) => {
if (!purchase.acknowledged) {
alert("purchase successful!");
finishTransactionAsync(purchase, true);
}
});
}
if (responseCode === IAPResponseCode.USER_CANCELED) {
alert("user cancelld!");
} else if (responseCode === IAPResponseCode.DEFERRED) {
alert("user does not have permission to buy");
} else {
alert("something went wrong: " + errorCode);
}
});
Then on my payment screen I have the following:
import { getProductsAsync, purchaseItemAsync } from "expo-in-app-purchases";
This is the code for the payment button:
const makePayment = async () => {
alert("now making payment...");
try {
const items = Platform.select({
ios: ["abc"],
android: ["my-sub-id", "sku-my-sub-id"],
});
alert("items: " + JSON.stringify(items));
const products = await getProductsAsync(items);
alert("products: " + JSON.stringify(products));
if (products.results.length > 0) {
alert("found products!");
await purchaseItemAsync("my-sub-id");
alert("done making payment!");
} else {
alert("no products..");
}
} catch (err) {
alert("error occured while trying to purchase: " + err);
}
};
In this case, getProductsAsync() does return something resembling the format the results should be. But it doesn't return any of the subscriptions I created (I copied the product ID value listed in that column and I supplied it to both getProductsAsync and purchaseItemAsync. I also supplied the url version which basically just has a prefix of sku-:
I also enabled licensing testing for the email I'm using in Google Play:
Do note that I'm uploading the .aab file to Google Play on the internal testing track then I install it using this URL format: https://play.google.com/apps/test/com.myname.appname/versionNumber
But when I open that link, it seems like google play is detecting it as an old version even though its the latest one. The changes I've made shows up though so I'm pretty sure that's the correct install URL.
What else could I be missing?
For anyone having the same problem. all you have to do is add this on your android/app/src/main/AndroidManifest.xml file:
<uses-permission android:name="com.android.vending.BILLING" />
It wasn't mentioned in the docs at the time of this post, and it doesn't get automatically added when you install a module. Might help save you the headache if you assume the same is true for all Expo modules.
Related
I have been trying to fix an issue with downloading a zip file from our service on our Android app. The iOS works fine, but on Android I get the following error:
ExceptionsManager.js:149 Error downloading file Error: Download manager failed to download from <download_urll> Status Code = 16
My download code looks like this. We are using react-native-blob-util:
const FILE_URL = uri;
const { config, fs } = ReactNativeBlobUtil;
const { dirs } = fs;
let writePath = dirs.DocumentDir;
if (Platform.OS === 'android') {
writePath = `${dirs.DownloadDir.split('Android')[0]}Download`;
}
const options = {
fileCache: true,
addAndroidDownloads: {
path: writePath + '/' + fileName + '.' + fileExtension,
description: `Downloading ${fileName}{}`,
notification: true,
useDownloadManager: true,
},
};
return await config(options)
.fetch('GET', FILE_URL, {
'Authorization': `Bearer ${authToken}`,
'Content-Type': 'application/zip',
})
.then(() => {
// Handle download success
})
.catch((e) => {
// Handle download failure
});
I have tried pretty much everything I found on the answers here, here, here and even here. And also several other places I found. I have checked the authentication too. It's not that.
Any idea what this might be? The issue is that this seems to occur only in the production variant of our Android app, and works just fine on the rest, including the production variant of our iOS app. A real head scratcher.
Any ideas are welcome. 🙏🏼
I'm in need of your expertise in React Native.
I'm trying to use expo-local-authentication for local fingerprint authentication for my application.
My project was created using expo init command.
I have done the setup as per the documentation and still running into a strange issue:
Below is the error I'm facing for LocalAuthentication.authenticateAsync(options):
Native method ExpoLocalAuthentication.authenticateAsync expects 0
arguments but received 1
Here is the required part of my code:
import * as LocalAuthentication from 'expo-local-authentication';
const authenticate = async () => {
const hasHardwareAsync = await LocalAuthentication.hasHardwareAsync();
if (hasHardwareAsync) {
const supportedAuthentications = await LocalAuthentication.supportedAuthenticationTypesAsync();
if (supportedAuthentications.indexOf(1) !== -1) {
// Finger print supported
const isFingerprintEnrolled = await LocalAuthentication.isEnrolledAsync();
if (isFingerprintEnrolled) {
const options = {
promptMessage: 'Authenticate yourself',
};
try {
// Also tried with await but it throws the same error
// await LocalAuthentication.authenticateAsync(options)
LocalAuthentication.authenticateAsync(options).then(result => {
// I never get inside this block
console.warn(result)
})
.catch(error => {
console.warn('Authentication Error: ', error)
})
} catch (error) {
console.warn(error)
}
}
}
}
}
Not sure what I'm missing. Seems like there is no information available about the error. I also tried to run the LocalAuthentication.authenticateAsync() without any arguments but it still throws the same error.
Any help on what could be the root cause of the issue and how can I resolve it or any other alternative for local authentication would be highly appreciated.
Update your app to the latest version of expo (38 in my case) and to the latest version of expo-local-authentication, and the error goes away.
I want to integrate Stripe 3d secure in my react native app. Using this lib: https://github.com/tipsi/tipsi-stripe, and with simple payment it works well. But with 3D I have several problem on iOS and also on Android:
iOS: createCardSource: true (at line 7 crashing the app).(Solved)
iOS: Stucked before redirection on the secure page
Android: How I know if the user paid or decline the payment at the remote page?(At line 27 no any data in the secure3dSourceResponse object)
import stripe from "tipsi-stripe";
paymentRequest = async (mutation, deal) => {
let paymentRequest;
try {
paymentRequest = await stripe.paymentRequestWithCardForm({
...options,
createCardSource: true
});
//iOS and Android gets back different objects.
const threeDSecure = Platform.OS === "android"
? paymentRequest.card.three_d_secure
: paymentRequest.details.three_d_secure;
if (
threeDSecure === "recommended"
|| threeDSecure === "required"
) {
let prefix = Platform.OS === "android"
? `appName://appName/`
: `appName://`;
let secure3dSourceResponse = null;
try {
const { dealFeeUSD } = this.state;
// On iOS the process stucked here, without any error message
secure3dSourceResponse = await stripe.createSourceWithParams({
type: "threeDSecure",
amount: dealFeeUSD || 3000,
currency: "USD",
flow: "redirect",
returnURL: prefix,
card: paymentRequest.sourceId
});
// On android I have no any data in secure3dSourceResponse after Stripe returns from their page.
} catch (error) {
console.log('secure3dSourceResponse', secure3dSourceResponse)
}
} else {
if (paymentRequest && paymentRequest.tokenId) {
this.handlePayDeal(mutation, deal, paymentRequest.tokenId);
}
}
} catch (error) {
console.log("paymentRequest: " + JSON.stringify(error));
}
};
I am using a custom HTTP request class for adding a Authorization Header to all of my requests, this works fine on almost every android device. Wired thing now is that I got some customer complaints that they are getting the 'No internet connection' error although they have a working network connection (other apps work and the errors are transmitted to the Sentry servers also).
As I am using Sentry error tracking I found out that these customers are all getting the error because the timeout error is thrown after 10 seconds for the first request at app start.
I guessed that something has to be wrong with this request so I built an alpha version for a limited number of users to track down the error (I send the options of every request to Sentry), but the requests look fine.
Next guess was that something is wrong with cordova-plugin-nativestorage on these devices but as I am catching them it should at lease return an empty token. No clue how to fix it right now. Any advice is appreciated!
export class CustomRequest {
apiToken: string = '';
constructor(private http: Http) { }
protected request(options: any): Observable<any> {
// If Native Storage doens't find a token, return an empty
let errorNativeStorage$ = function (): Observable<any> {
return Observable.of({ Token: '' });
};
// Get Token form Native Storage
let token$ = Observable.fromPromise(NativeStorage.getItem('JWT'))
.catch(errorNativeStorage$);
// Handle request errors
let genericError$ = function (error: Response | any): Observable<any> {
let errMsg: string;
if (error instanceof Response) {
const body = error.json() || '';
const err = body.error || JSON.stringify(body);
errMsg = `${error.status} - ${error.statusText || ''} ${err}`;
} else {
errMsg = error.message ? error.message : error.toString();
}
console.error(errMsg);
Raven.captureException(error, { extra: { errorMsg: errMsg } });
return Observable.of({
Errors: { General: 'No internet connection.' }
});
};
// the request
let request$ = (options) => {
return this.http.request(new Request(options))
.retryWhen(error => error.delay(1000))
.timeout(10000, new Error('timeout'))
.map((res: Response) => res.json())
.catch(genericError$);
};
// get the token and build request
return token$
.map(jwt => {
if (options.body) {
if (typeof options.body !== 'string') {
options.body = JSON.stringify(options.body);
}
options.body = options.body.replace(/ /g, '').replace(/\r?\n|\r/g, '');
}
options.headers = new Headers({
'Content-Type': 'application/x-www-form-urlencoded, application/json'
});
if (jwt.Token) {
options.headers.append('Authorization', `Bearer ${jwt.Token}`);
}
Raven.captureMessage('request options', { level: 'info', environment: 'live', extra: options });
return options;
})
.switchMap(options => request$(options));
}
}
I am using:
Ionic 2.0.0-beta.11
Angular 2.0.0-rc.4
Most recent version of NativeStorage plugin from github
Devices with the error (only two examples, there are more):
Samsung SM-N910F (Webview: Chrome Mobile 53.0.2785, Android 6.0.1)
Samsung SM-G800F (Webview: Chrome Mobile 53.0.2785, Android 5.1.1)
If somebody's interested: The root cause was that people that upgraded Android somehow lost the chrome webview app and Angular was not working without one (of course). I solved it by packaging the crosswalk-webview in my app!
I'm using react-native for facebook log in. As you can see on the following link, i have some problem with the facebook login render:
To get that, first i did : rnpm install react-native-fbsdk (to install facebook dependencies).
Then I added the basics facebook code tutorial (https://developers.facebook.com/docs/react-native/login) :
const FBSDK = require('react-native-fbsdk');
const {
LoginButton,
} = FBSDK;
var Login = React.createClass({
render: function() {
return (
<View>
<LoginButton
publishPermissions={["publish_actions"]}
onLoginFinished={
(error, result) => {
if (error) {
alert("Login failed with error: " + result.error);
} else if (result.isCancelled) {
alert("Login was cancelled");
} else {
alert("Login was successful with permissions: " + result.grantedPermissions)
}
}
}
onLogoutFinished={() => alert("User logged out")}/>
</View>
);
}
});
Finally I added my Facebook API Key in the android manifest like that :
(meta-data) android:name="com.facebook.sdk.ApplicationId" android:value="#string/facebook_app_id"/>
Do you have any idea to solve this problem ? Thanks a lot for your answers !
I found the solution...
The problem was coming from MainApplication.java and MainActivity.java (in android/app).
Facebook's documentation is out of date... (only available for react-native version < 0.29...).