I am trying to set a new track and upload APK for publishing using nodejs... But no APK is uploaded nor is there a track set.
But the results from the validation and all the other steps are exactly as I expect(according to API). The script succeeds, yet nothing changes in the developer console... So is there a magic guru out there that may see my issue?
NOTE 1: I did publish my app manually because the android-publisher is only for updating apps that have been published before
NOTE 2: This worked before(a long time ago), and has stopped working for a while, I finally got around to updating everything
My code:
var google = require('googleapis').google;
var Promise = require('bluebird');
var _ = require('lodash');
var settings = require('./config/settings.json');
// Enable API access into the Developer Console: https://play.google.com/apps/publish/?account=7639196906174529268#ApiAccessPlace
// Create a service account
// Download the JSON and save it here
// Make sure the email of the JSON is added to the apps for release manager role:
// https://play.google.com/apps/publish/?account=7639196906174529268#AdminPlace
var key = require('./config/google-play-user.json');
// editing "scope" allowed for OAuth2
var scopes = [
'https://www.googleapis.com/auth/androidpublisher'
];
var OAuth2 = google.auth.OAuth2;
var oauth2Client = new OAuth2();
var jwtClient = new google.auth.JWT(key.client_email, null, key.private_key, scopes, null);
var play = google.androidpublisher({
version: 'v3',
auth: oauth2Client,
params: {
// default options
// this is the package name for your initial app you've already set up on the Play Store
packageName: settings.app.id
}
});
google.options({auth: oauth2Client});
// Start with a new edit.
startEdit().then(function(data) {
// Load our APK(in this case a Cordova APK).
var standardAPK = require('fs').readFileSync('./platforms/android/build/outputs/apk/android-release.apk');
// Stage the upload (doesn't actually upload anything).
return upload({
edit: data.edit,
apk: standardAPK,
key: 'standardApk'
});
}).then(function(data) {
// Set our track.
return setTrack(data);
}).then(function(data) {
// Validate our changes.
return validateToPlayStore(data);
}).then(function(data) {
console.log('Successful uploaded APK files:', data);
}).catch(function(err) {
console.log(err);
process.exit(1);
});
/**
* Sets our authorization token and begins an edit transaction.
*/
function startEdit() {
return new Promise(function(resolve, reject) {
jwtClient.authorize(function(err, tokens) {
if (err) {
console.log(err);
process.exit(1);
return;
}
// Set the credentials before we doing anything.
oauth2Client.setCredentials(tokens);
play.edits.insert({
packageName: settings.app.id
}, function(err, edit) {
if (err || !edit) { reject(err); }
resolve({
edit: edit.data
});
});
});
});
}
/**
* Stages an upload of the APK (but doesn't actually upload anything)
*/
function upload(data) {
var edit = data.edit;
var apk = data.apk;
var key = data.key;
return new Promise(function(resolve, reject) {
play.edits.apks.upload({
editId: edit.id,
packageName: settings.app.id,
media: {
mimeType: 'application/vnd.android.package-archive',
body: apk
}
}, function(err, res) {
if (err || !res) { reject(err); }
// Pass any data we care about to the next function call.
var obj = {};
obj[key] = res.data;
resolve(_.omit(_.extend(data, obj), 'apk'));
});
});
}
/**
* Sets our track (beta, production, etc.)
*/
function setTrack(data) {
var edit = data.edit;
var track = 'production';
return new Promise(function(resolve, reject) {
play.edits.tracks.update({
editId: edit.id,
track: track,
packageName: settings.app.id
}, function(err, res) {
if (err || !res) { reject(err); }
resolve(_.extend(data, {setTrackResults: res.data}));
});
});
}
/**
* Validates our edit transaction and makes our changes live.
*/
function validateToPlayStore(data) {
return new Promise(function(resolve, reject) {
// play.edits.commit({ // Commit will set the change LIVE
play.edits.validate({ // Validate will only validate it, not set it LIVE.
editId: data.edit.id,
packageName: settings.app.id
}, function(err, res) {
if (err || !res) { reject(err); }
resolve(_.extend(data, {validateToPlayStoreResults: res.data}));
});
});
}
Originally from http://frontendcollisionblog.com/javascript/2015/12/26/using-nodejs-to-upload-app-to-google-play.html when it still worked
You can use apkup to upload APKs to Google Play. apkup is basically a friendly wrapper/CLI for the googleapis package.
CLI example:
npx apkup \
--key ./config/google-play-user.json \
--release-notes "en-US=lorem ipsum dolor" \
--apk ./platforms/android/build/outputs/apk/android-release.apk
Node.js example:
const { Apkup } = require('apkup')
const key = require('./config/google-play-user.json')
const apkup = new Apkup(key)
apkup
.upload('./platforms/android/build/outputs/apk/android-release.apk', {
releaseNotes: [
{
language: 'en-US',
text: 'Minor bug fixes...'
}
]
})
.then(data => {
console.log(` > ${data.packageName} version ${data.versionCode} is up!`)
})
Related
I hope you are doing well.
I have a problem when I connect with google on the emulator with android.
If I go through Expo Go on either Android or Ios, it works fine. But when I build my apk, and I install it on the emulator it sends me back to the same login page without redirecting me to the application.
Do you have an idea of the origin of the problem?
My google login function :
try {
const result = await promptAsync();
if (result.type === "success") {
/* `accessToken` is now valid and can be used to get data from the Google API with HTTP requests */
const { id_token } = result.params;
const provider = new firebase.auth.GoogleAuthProvider();
const credential =
firebase.auth.GoogleAuthProvider.credential(id_token);
auth.signInWithCredential(credential)
.then((res) => {
const user = res.additionalUserInfo.profile;
let action = addUserOnFirestore(
res.user?.uid,
user.email,
user.given_name,
user.family_name,
user.picture,
res
);
setIsLoading(true);
try {
dispatch(action);
} catch (err) {
setError(err.message);
}
setIsLoading(false);
})
.catch((error) => {
console.log("firebase cred err:", error);
});
} else {
console.log("cancelled");
}
} catch (e) {
console.log("general error : ", e);
return { error: true };
}
}
And the properties define :
const [request, response, promptAsync] = Google.useIdTokenAuthRequest({
clientId: "XXXX",
iosClientId: "XXX",
androidClientId: "XXX",
androidStandaloneAppClientId: "XXX",
redirectUri: Platform.select({
// iOS handles redirectUri perfectly fine on it's own
ios: undefined,
// Due to Expo's bug, we need to manually encode the redirectUri
// https://github.com/expo/expo/issues/12044
android: makeRedirectUri({
// intent filter set up in app.config.js
// must be the same as "package name" in Google Cloud Console
native: 'packagename://oauthredirect',
}),
})
});
Thanks in advance for your responses.
Using the same Flutter code on both Android and iOS, restores work on iOS, but not on Android.
I’m able to buy non-consumable products on both platforms, but when I try to restore them on Android, I always get zero as a return value.
This is how I initialize the IAP:
#override
initState() {
final Stream<List<PurchaseDetails>> purchaseUpdated = InAppPurchase.instance.purchaseStream;
_subscription = purchaseUpdated.listen((purchaseDetailsList) {
_listenToPurchaseUpdatedBooks(purchaseDetailsList);
}, onDone: () {
_subscription.cancel();
}, onError: (error) {
// handle error here.
});
super.initState();
}
For purchasing, I used the following code:
late PurchaseParam purchaseParam;
List<PurchaseDetails> _purchases = <PurchaseDetails>[];
InAppPurchase _inAppPurchase = InAppPurchase.instance;
bool isIOS = Theme
.of(context)
.platform == TargetPlatform.iOS;
if (isIOS == false) {
final Map<String, PurchaseDetails> purchasesMap =
Map<String, PurchaseDetails>.fromEntries(
_purchases.map((PurchaseDetails purchase) {
if (purchase.pendingCompletePurchase) {
_inAppPurchase.completePurchase(purchase);
}
return MapEntry<String, PurchaseDetails>(
purchase.productID, purchase);
}));
final GooglePlayPurchaseDetails? oldSubscription = _getOldSubscription(
productL, purchasesMap);
purchaseParam = GooglePlayPurchaseParam(
productDetails: productL,
applicationUserName: null,
changeSubscriptionParam: (oldSubscription != null)
? ChangeSubscriptionParam(
oldPurchaseDetails: oldSubscription,
prorationMode: ProrationMode
.immediateWithTimeProration,
)
: null);
} else {
purchaseParam = PurchaseParam(
productDetails: productL,
applicationUserName: null,
);
}
await InAppPurchase.instance.buyConsumable(purchaseParam: purchaseParam)
.catchError((e) {
print('Got error: $e'); // Finally, callback fires.
return false; // Future completes with 42.
}).then((value) {
if (value == true) {
print('The value is $value');
}
});
I expected to get a value different from zero for restored purchases, but on Android, I always get zero as a return value.
On Android, I am also checking for old subscriptions, as I am required to do.
In order for in-app purchases to register on Android, does my code need to be further adapted for Android? We are running the app under "Closed testing", all of our test users are configured under "License testing" in the Play Console, and we are using version 3.0.2 of the in_app_purchase library. Many thanks!
I'm new to bitnami so setting up the email adapter is proving quite difficult for my android app, any assistance would be appreciated. My goal is to send a password reset to a user after they enter their email address on my app.
What Have I done so far
Created a custom domain and setting up mailgun, acquiring the API key
Set up AWS and once Bitnami was up and running connected Parse to my app
I made sure parse-server-simple-mailgun-adapter was installed
Since app folder and server.js file does not exist anymore when searching through previous forums I placed the below code into this index.js file - /opt/bitnami/parse/node_modules/parse-server/lib/index.js
I taken this code from the following entry https://github.com/ParsePlatform/parse-server
The code currently looks like this in my index.js file:
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
Object.defineProperty(exports, "S3Adapter", {
enumerable: true,
get: function () {
return _s3FilesAdapter.default;
}
});
Object.defineProperty(exports, "FileSystemAdapter", {
enumerable: true,
get: function () {
return _fsFilesAdapter.default;
}
});
Object.defineProperty(exports, "InMemoryCacheAdapter", {
enumerable: true,
get: function () {
return _InMemoryCacheAdapter.default;
}
});
Object.defineProperty(exports, "NullCacheAdapter", {
enumerable: true,
get: function () {
return _NullCacheAdapter.default;
}
});
Object.defineProperty(exports, "RedisCacheAdapter", {
enumerable: true,
get: function () {
return _RedisCacheAdapter.default;
}
});
Object.defineProperty(exports, "LRUCacheAdapter", {
enumerable: true,
get: function () {
return _LRUCache.default;
}
});
Object.defineProperty(exports, "PushWorker", {
enumerable: true,
get: function () {
return _PushWorker.PushWorker;
}
});
Object.defineProperty(exports, "ParseGraphQLServer", {
enumerable: true,
get: function () {
return _ParseGraphQLServer.ParseGraphQLServer;
}
});
exports.TestUtils = exports.ParseServer = exports.GCSAdapter = exports.default = void
var _ParseServer2 = _interopRequireDefault(require("./ParseServer"));
var _s3FilesAdapter = _interopRequireDefault(require("#parse/s3-files-adapter"));
var _fsFilesAdapter = _interopRequireDefault(require("#parse/fs-files-adapter"));
var _InMemoryCacheAdapter = _interopRequireDefault(require("./Adapters/Cache/InMemoryC
var _NullCacheAdapter = _interopRequireDefault(require("./Adapters/Cache/NullCacheAdap
var _RedisCacheAdapter = _interopRequireDefault(require("./Adapters/Cache/RedisCacheAd
var _LRUCache = _interopRequireDefault(require("./Adapters/Cache/LRUCache.js"));
var TestUtils = _interopRequireWildcard(require("./TestUtils"));
exports.TestUtils = TestUtils;
var _deprecated = require("./deprecated");
var _logger = require("./logger");
var _PushWorker = require("./Push/PushWorker");
var _Options = require("./Options");
var _ParseGraphQLServer = require("./GraphQL/ParseGraphQLServer");
var server = ParseServer({
...otherOptions,
// Enable email verification
verifyUserEmails: true,
// if `verifyUserEmails` is `true` and
// // if `emailVerifyTokenValidityDuration` is `undefined` then
// // email verify token never expires
// // else
// // email verify token expires after `emailVerifyTokenValidityDuration`
// //
// // `emailVerifyTokenValidityDuration` defaults to `undefined`
// email verify token below expires in 2 hours (= 2 * 60 * 60 == 7200 seconds)
// emailVerifyTokenValidityDuration: 2 * 60 * 60, // in seconds (2 hours = 7200 seconds)
// set preventLoginWithUnverifiedEmail to false to allow user to login without verifying their email
// // set preventLoginWithUnverifiedEmail to true to prevent user from login if their email is not verified
preventLoginWithUnverifiedEmail: false, // defaults to false
// The public URL of your app.
// // This will appear in the link that is used to verify email addresses and reset passwords.
// // Set the mount path as it is in serverURL
publicServerURL: 'https://xxxxxxxx/xxxxx/',
// Your apps name. This will appear in the subject and body of the emails that are sent.
appName: 'parse-server',
// The email adapter
emailAdapter: {
module: '#parse/simple-mailgun-adapter',
options: {
// The address that your emails come from
fromAddress: 'xxxxxxxxxxxxxxxxxxxxxxxxx',
// Your domain from mailgun.com
domain: 'xxxxxxxxxxxxxxx',
// Your API key from mailgun.com
apiKey: 'xxxxxxxxxxxxxxxxxxxxxxxxxx',
}
},
// account lockout policy setting (OPTIONAL) - defaults to undefined
// // if the account lockout policy is set and there are more than `threshold` number of failed login attempts
r code `Parse.Error.OBJECT_NOT_FOUND` with error message `Your account is locked due to multiple failed login attempts.
ute(s)`. After `duration` minutes of no login attempts, the application will allow the user to try login again.
accountLockout: {
duration: 5, // duration policy setting determines the number of minutes that a locked-out account remains locked out before automatically becom
nlocked. Set it to a value greater than 0 and less than 100000.
threshold: 3, // threshold policy setting determines the number of failed sign-in attempts that will cause a user account to be locked. Set it
n integer value greater than 0 and less than 1000.
},
// optional settings to enforce password policies
passwordPolicy: {
// Two optional settings to enforce strong passwords. Either one or both can be specified.
// // If both are specified, both checks must pass to accept the password
// // 1. a RegExp object or a regex string representing the pattern to enforce
validatorPattern: /^(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.{8,})/, // enforce password with at least 8 char with at least 1 lower case, 1 upper case and 1
t
// 2. a callback function to be invoked to validate the password
validatorCallback: (password) => { return validatePassword(password) },
doNotAllowUsername: true, // optional setting to disallow username in passwords
maxPasswordAge: 90, // optional setting in days for password expiry. Login fails if user does not reset the password within this period after signup/l
eset.
maxPasswordHistory: 5, // optional setting to prevent reuse of previous n passwords. Maximum value that can be specified is 20. Not specifying it or spe
ng 0 will not enforce history.
//optional setting to set a validity duration for password reset links (in seconds)
resetTokenValidityDuration: 24*60*60, // expire after 24 hours
}
});
function _getRequireWildcardCache() { if (typeof WeakMap !== "function") return null; var cache = new WeakMap(); _getRequireWildcardCache = function () { ret
urn cache; }; return cache; }
function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function")
{ return { default: obj }; } var cache = _getRequireWildcardCache(); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasProperty
Descriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { var desc
= hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); }
else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
// Factory function
const _ParseServer = function (options) {
const server = new _ParseServer2.default(options);
return server.app;
}; // Mount the create liveQueryServer
exports.ParseServer = _ParseServer;
_ParseServer.createLiveQueryServer = _ParseServer2.default.createLiveQueryServer;
_ParseServer.start = _ParseServer2.default.start;
const GCSAdapter = (0, _deprecated.useExternal)('GCSAdapter', '#parse/gcs-files-adapter');
exports.GCSAdapter = GCSAdapter;
Object.defineProperty(module.exports, 'logger', {
get: _logger.getLogger
});
var _default = _ParseServer2.default;
exports.default = _default;
My config.json file looks like this(Config file located /opt/bitnami/parse) :
{
"appId": "myapp",
"masterKey": "NTxxxxxxmm",
"appName": "parse-server",
"mountPath": "/parse",
"port": "1337",
"host": "0.0.0.0",
"serverURL": "http://3.1xxxxx5/parse/",
"databaseURI": "mongodb://bn_parse:TFyNVz7Y45#127.xxxxx:27xx7/bitnami_parse"
}
When I run my app with the following code:
ParseUser.requestPasswordResetInBackground("myemail#gmail.com",
new RequestPasswordResetCallback() {
public void done(ParseException e) {
if (e == null) {
// An email was successfully sent with reset instructions.
Toast.makeText(getApplicationContext(),
"Password Reset email has been sent to this email address",
Toast.LENGTH_LONG).show();
} else {
// Something went wrong. Look at the ParseException to see what's up.
Log.i("Error", "Password Reset Error", e);
Toast.makeText(getApplicationContext(), e.getMessage(), Toast.LENGTH_SHORT).show();
Toast.makeText(getApplicationContext(),
"Saving user failed.", Toast.LENGTH_SHORT).show();
}
}
});
I get the following Error message in the logs:
2020-06-13 23:13:05.704 15647-15647/com.example.Fitness I/Error: Password Reset Error
com.parse.ParseRequest$ParseRequestException: bad json response
at com.parse.ParseRequest.newTemporaryException(ParseRequest.java:290)
at com.parse.ParseRESTCommand.onResponseAsync(ParseRESTCommand.java:308)
at com.parse.ParseRESTUserCommand.onResponseAsync(ParseRESTUserCommand.java:126)
at com.parse.ParseRequest$3.then(ParseRequest.java:137)
at com.parse.ParseRequest$3.then(ParseRequest.java:133)
at bolts.Task$15.run(Task.java:917)
at bolts.BoltsExecutors$ImmediateExecutor.execute(BoltsExecutors.java:105)
at bolts.Task.completeAfterTask(Task.java:908)
at bolts.Task.continueWithTask(Task.java:715)
at bolts.Task.continueWithTask(Task.java:726)
at bolts.Task$13.then(Task.java:818)
at bolts.Task$13.then(Task.java:806)
at bolts.Task$15.run(Task.java:917)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1162)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:636)
at java.lang.Thread.run(Thread.java:764)
Caused by: org.json.JSONException: Value <!DOCTYPE of type java.lang.String cannot be converted to JSONObject
at org.json.JSON.typeMismatch(JSON.java:111)
at org.json.JSONObject.<init>(JSONObject.java:163)
at org.json.JSONObject.<init>(JSONObject.java:176)
at com.parse.ParseRESTCommand.onResponseAsync(ParseRESTCommand.java:298)
at com.parse.ParseRESTUserCommand.onResponseAsync(ParseRESTUserCommand.java:126)
at com.parse.ParseRequest$3.then(ParseRequest.java:137)
at com.parse.ParseRequest$3.then(ParseRequest.java:133)
at bolts.Task$15.run(Task.java:917)
at bolts.BoltsExecutors$ImmediateExecutor.execute(BoltsExecutors.java:105)
at bolts.Task.completeAfterTask(Task.java:908)
at bolts.Task.continueWithTask(Task.java:715)
at bolts.Task.continueWithTask(Task.java:726)
at bolts.Task$13.then(Task.java:818)
at bolts.Task$13.then(Task.java:806)
at bolts.Task$15.run(Task.java:917)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1162)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:636)
at java.lang.Thread.run(Thread.java:764)
2020-06-13 23:13:05.754 1441-1441/? D/gralloc_ranchu: gralloc_alloc: Creating ashmem region of size 360448
It looks like the email address which is a string cannot be converted to a JSON object, I've checked and made sure that my server URL has a "/" at the end of the URL. I also added 'com.parse:parse-android:1.12.0' into my gradle to see if that would fix it but the issue is still occurring.
If I've added these entries in the wrong index.js file or need to format the file can someone let me know what I'm doing wrong?
The code you copied and pasted is unfortunately rather difficult to follow, but the error
Caused by: org.json.JSONException: Value
indicates that the URL your Android app is reaching is not returning JSON data. Your Android code is either not hitting the proper URL to invoke the function, or your server is misconfigured and is returning HTML (probably an error page or a 404) to the Android app. Unless you've tested and verified that you can successfully invoke a basic function that returns JSON, I'd suggest removing all the email-related code and working on that first, before trying to put that back in.
I am creating an App Where user can buy coins and for that I have been trying to integrate Razorpay into my Android App since a long time now. Razorpay can directly be used in Android. It sends Success or Failure results for payment and I can act accordingly (adding points to database in this case). But the problem with this approach is that I have to write points (after success) to database from the app. Which means I have to give write access for points node to user app which is not a good idea. So I wanted to use Razorpay with Firebase Cloud Functions and searching for a long time I came across this tutorial which is for web. I am quite new to Cloud Functions and hence wanted a little help for Android.
Here is the Index.js code but For Web
const functions = require("firebase-functions");
var express = require("express");
var cors = require("cors");
var request = require("request");
const crypto = require("crypto");
const key = "----insert yout key here----";
const key_secret = "----- insert key secret here ----";
var app = express();
app.use(cors({ origin: true }));
app.post("/", (req, res) => {
const amount = req.body.amount;
//Allow Api Calls from local server
const allowedOrigins = [
"http://127.0.0.1:8080",
"http://localhost:8080",
"https://-------YourFirebaseApp-----.firebaseapp.com/"
];
const origin = req.headers.origin;
if (allowedOrigins.indexOf(origin) > -1) {
res.setHeader("Access-Control-Allow-Origin", origin);
}
var options = {
method: "POST",
url: "https://api.razorpay.com/v1/orders",
headers: {
//There should be space after Basic else you get a BAD REQUEST error
Authorization:
"Basic " + new Buffer(key + ":" + key_secret).toString("base64")
},
form: {
amount: amount,
currency: "INR",
receipt:
"----- create a order in firestore and pass order_unique id here ---",
payment_capture: 1
}
};
request(options, function(error, response, body) {
if (error) throw new Error(error);
res.send(body);
});
});
app.post("/confirmPayment", (req, res) => {
const order = req.body;
const text = order.razorpay_order_id + "|" + order.razorpay_payment_id;
var signature = crypto
.createHmac("sha256", key_secret)
.update(text)
.digest("hex");
if (signature === order.razorpay_signature) {
console.log("PAYMENT SUCCESSFULL");
res.send("PAYMENT SUCCESSFULL");
} else {
res.send("something went wrong!");
res.end();
}
});
exports.paymentApi = functions.https.onRequest(app);
I think this will help you.
In my case, I am accessing items(Array of Product IDs) from the user's cart and reading the current price of the items then passing it as an argument to SendOrderId function which will return an OrderId to proceed.
The important thing to keep in mind is that you must have added razorpay in your dependencies inside package.json. You can do that by simply running
npm i razorpay
inside your functions folder (Which include index.js) which will automatically add the dependency to your project
const functions = require("firebase-functions");
const admin = require('firebase-admin');
const Razorpay = require('razorpay')
const razorpay = new Razorpay({
key_id: 'Your_razorpay_key_id',
key_secret: 'your_secret'
})
admin.initializeApp();
function SendOrderId(amountData, response) {
var options = {
amount: amountData, // amount in the smallest currency unit
currency: "INR",
};
razorpay.orders.create(options, function(err, order) {
console.log(order);
response.send(order);
});
}
exports.getOrderId = functions.https.onRequest((req, res) => {
return admin.firestore().collection('Users').doc(req.query.uid).get().then(queryResult => {
console.log(queryResult.data().Cart);
admin.firestore().collectionGroup("Products").where('ProductId', 'in', queryResult.data().Cart).get().then(result => {
var amount = 0;
result.forEach(element => {
amount += element.data().price;
});
SendOrderId(amount * 100, res);
})
})
});
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));
}
};