I have set up a payment system for my app through PayPal using both Android and IOS.
With the click of a button, the method 'payoutRequest' is called and sends the necessary information to PayPal through Firebase Functions.
The code I have works perfectly with Android but not so much with iOS.
Android's method: payoutRequest
public static final MediaType MEDIA_TYPE = MediaType.parse("application/json");
ProgressDialog progress;
private void payoutRequest() {
progress = new ProgressDialog(this);
progress.setTitle("Processing your payout ...");
progress.setMessage("Please Wait .....");
progress.setCancelable(false);
progress.show();
// HTTP Request ....
final OkHttpClient client = new OkHttpClient();
// in json - we need variables for the hardcoded uid and Email
JSONObject postData = new JSONObject();
try {
postData.put("uid", FirebaseAuth.getInstance().getCurrentUser().getUid());
postData.put("email", mPayoutEmail.getText().toString());
} catch (JSONException e) {
e.printStackTrace();
}
// Request body ...
RequestBody body = RequestBody.create(MEDIA_TYPE, postData.toString());
// Build Request ...
final Request request = new Request.Builder()
.url("https://us-central1-myapp.cloudfunctions.net/payout")
.post(body)
.addHeader("Content-Type", "application/json")
.addHeader("cache-control", "no-cache")
.addHeader("Authorization", "Your Token")
.build();
client.newCall(request).enqueue(new Callback() {
#Override
public void onFailure(Call call, IOException e) {
// something went wrong right off the bat
progress.dismiss();
}
#Override
public void onResponse(Call call, Response response) throws IOException {
// response successful ....
// refers to response.status('200') or ('500')
int responseCode = response.code();
if (response.isSuccessful()) {
switch(responseCode) {
case 200:
Snackbar.make(findViewById(R.id.layout),
"Payout Successful!", Snackbar.LENGTH_LONG)
.show();
break;
case 500:
Snackbar.make(findViewById(R.id.layout),
"Error: no payout available", Snackbar
.LENGTH_LONG).show();
break;
default:
Snackbar.make(findViewById(R.id.layout),
"Error: couldn't complete the transaction",
Snackbar.LENGTH_LONG).show();
break;
}
} else {
Snackbar.make(findViewById(R.id.layout),
"Error: couldn't complete the transaction",
Snackbar.LENGTH_LONG).show();
}
progress.dismiss();
}
});
}
The above method sends the html request to PayPal through the Firebase Function created, see below (firebase function):
index.js - JavaScript file used with Firebase Functions**
'use strict';
const functions = require('firebase-functions');
const paypal = require('paypal-rest-sdk');
const admin = require('firebase-admin');
admin.initializeApp(functions.config().firebase);
paypal.configure({
mode: 'sandbox',
client_id: functions.config().paypal.client_id,
client_secret: functions.config().paypal.client_secret
})
exports.newRequest = functions.database.ref('/history/{pushId}').onCreate((snapshot, context) => {
var requestSnapshot = snapshot.val();
var price = snapshot.child('ridePrice').val();
var pushId = context.params.pushId;
return snapshot.ref.parent.child(pushId).child('price').set(price);
});
function getPayoutsPending(uid) {
return admin.database().ref('Users/Drivers/' + uid + '/history').once('value').then((snap) => {
if(snap === null){
throw new Error("profile doesn't exist");
}
var array = [];
if(snap.hasChildren()){
snap.forEach(element => {
if (element.val() === true) {
array.push(element.key);
}
});
}
return array;
}).catch((error) => {
return console.error(error);
});
}
function getPayoutsAmount(array) {
return admin.database().ref('history').once('value').then((snap) => {
var value = 0.0;
if(snap.hasChildren()){
snap.forEach(element => {
if(array.indexOf(element.key) > -1) {
if(element.child('price').val() !== null){
value += element.child('price').val();
}
}
});
return value;
}
return value;
}).catch((error) => {
return console.error(error);
});
}
function updatePaymentsPending(uid, paymentId) {
return admin.database().ref('Users/Drivers/' + uid + '/history').once('value').then((snap) => {
if(snap === null){
throw new Error("profile doesn't exist");
}
if(snap.hasChildren()){
snap.forEach(element => {
if(element.val() === true) {
admin.database().ref('Users/Drivers/' + uid + '/history/' + element.key).set( {
timestamp: admin.database.ServerValue.TIMESTAMP,
paymentId: paymentId
});
admin.database().ref('history/' + element.key + '/driverPaidOut').set(true);
}
});
}
return null;
}).catch((error) => {
return console.error(error);
});
}
exports.payout = functions.https.onRequest((request, response) => {
return getPayoutsPending(request.body.uid)
.then(array => getPayoutsAmount(array))
.then(value => {
var valueTrunc = parseFloat(Math.round((value * 0.75) * 100) / 100).toFixed(2);
const sender_batch_id = Math.random().toString(36).substring(9);
const sync_mode = 'false';
const payReq = JSON.stringify({
sender_batch_header: {
sender_batch_id: sender_batch_id,
email_subject: "You have a payment"
},
items: [
{
recipient_type: "EMAIL",
amount: {
value: valueTrunc,
currency: "CAD"
},
receiver: request.body.email,
note: "Thank you.",
sender_item_id: "Ryyde Payment"
}
]
});
return paypal.payout.create(payReq, sync_mode, (error, payout) => {
if (error) {
console.warn(error.response);
response.status('500').end();
throw error;
}
console.info("payout created");
console.info(payout);
return updatePaymentsPending(request.body.uid, sender_batch_id)
});
}).then(() => {
response.status('200').end();
return null;
}).catch(error => {
console.error(error);
});
});
When I run the above code in Android, it works and does all it should but the below method isn't working for IOS (same index.js file used for both platforms).
iOS's method: payoutRequest:
func payoutRequest() {
print("payoutRequest")
// Progress View
self.progress.progress = value
self.perform(#selector(updateProgressView), with: nil, afterDelay: 1.0)
let email = txtPayoutEmail.text!
let url = "https://us-central1-myapp.cloudfunctions.net/payout"
let params : Parameters = [
"uid": self.uid! as String,
"email": email
]
let headers : HTTPHeaders = [
"Content-Type": "application/json",
"Authorization": "Your Token",
"cache-control": "no-cache"
]
let myData = try? JSONSerialization.data(withJSONObject: params, options: [])
print("data: \(String(describing: myData))")
Alamofire.request(url, method: .post, parameters: params, encoding: JSONEncoding.default, headers: headers)
.validate(statusCode: 200..<300)
.validate(contentType: ["application/json"])
.responseData(completionHandler: { (response) in
//print("Request: \(String(describing: response.request))") // original url request
//print("Response: \(String(describing: response.response))") // http url response
print("Result: \(response.result)") // response serialization result
switch response.result {
case .success:
print("Validation Successful")
let parsedObject = try! JSONSerialization.jsonObject(with: myData!, options: .allowFragments)
print("parsed: \(parsedObject)")
case .failure(let error):
print(error)
}
})
}
Also the following url is not completely accurate for privacy concerns.
https://us-central1-myapp.cloudfunctions.net/payout (myapp) is not name of the app
I have narrowed down the issue to the actual method in iOS - payoutRequest is not sending the post html request as it should.
What could I be doing wrong?
EDIT
After running the code (payoutRequest()) below is the print() statement:
** name of app is blacked out **
Also, if I go to my PayPal developer account, go into the Dashboard and click on Notifications, I should get a notification for the following:
payment received in the main account (ok)
payment received from the riders email account (ok)
payment paid out to the driver (none)
mass payment to driver (none)
see:
Related
Is there any way that I can generate a Public URL without any expiry date and token? I am new in flutter and developing an application to upload the image into the AWS s3 but getting URL with token.
I'm using below code
Future<String> upload(String image,String filePath) async {
try {
print('In upload');
// Uploading the file with options
File local = File(image);
final key = filePath + DateTime.now().toString();
Map<String, String> metadata = <String, String>{};
metadata['name'] = 'filename';
metadata['desc'] = 'A test file';
S3UploadFileOptions options = S3UploadFileOptions(
accessLevel: StorageAccessLevel.guest, metadata: metadata);
UploadFileResult result = await Amplify.Storage.uploadFile(
key: key,
local: local,
options: options,
onProgress: (progress) {
print("PROGRESS: " + progress.getFractionCompleted().toString());
});
setState(() {
_uploadFileResult = result.key;
print(_uploadFileResult);
});
return await getUrl().then((value){
return value;
});
} catch (e) {
print('UploadFile Err: ' + e.toString());
}
return '';
}
for taking url
Future<String> getUrl() async {
try {
print('In getUrl');
String key = _uploadFileResult;
S3GetUrlOptions options = S3GetUrlOptions(
accessLevel: StorageAccessLevel.guest, expires: 10000);
GetUrlResult result =
await Amplify.Storage.getUrl(key: key, options: options);
setState(() {
_getUrlResult = result.url;
print(_getUrlResult);
});
return _getUrlResult;
} catch (e) {
print('GetUrl Err: ' + e.toString());
}
return '';
}
I am trying to get heart rate data from Google Fit using this codes:
void addBPM(List<FitData> results) {
setState(() {
results.where((data) => !data.userEntered).forEach((result) =>
_heartBPM = result.value.round());
});
}
void clearBPM() {
setState(() {
_heartBPM = 0;
});
}
void revokePermissions() async {
try {
await FitKit.revokePermissions();
permissions = await FitKit.hasPermissions(DataType.values);
print('revokePermissions: success');
} catch (e) {
print('revokePermissions: $e');
}
}
void read() async {
try {
permissions = await FitKit.requestPermissions(DataType.values);
if (!permissions) {
print('requestPermissions: failed');
setState(() {
bodyHBPM = '0';
});
} else {
if(await FitKit.requestPermissions(DataType.values)){
final now = DateTime.now();
final startOfDay = DateTime(now.year, now.month, now.day);
final results = await FitKit.read(
DataType.HEART_RATE,
dateFrom: startOfDay,
dateTo: now,
);
startTimer();
// print(results);
clearBPM();
addBPM(results);
}
print('readAll: success');
}
} catch (e) {
print('readAll: $e');
}
}
It was successful a week ago until I ran it again today and got this error:
readAll: PlatformException(FitKit, 5000: Application needs OAuth consent from the user, null, null)
What am I missing? The OAuth credentials have been created in the Google Console and it hasn't been changed since the last time I got it working.
I have the following firebase cloud function written in node.js that I call from my Android app
exports.findNearestBranch = functions.https.onCall((data, context) => {
var latitutde = data.lat;
var longitude = data.long;
var ret;
return getLocationObject(latitutde,longitude)
.then(function(result){
var fromObject=result;
console.log('CONTEXT CLIENT '+latitutde+' LONG '+longitude);
calculateNearestBranch(fromObject)
.then(function(result){
console.log("TO APP "+JSON.stringify(result));
ret=result;
})
.catch(function(error){
});
})
.catch(function(error){
});
});
The function works fine but I get null when trying to get results in Android with the following method
private Task<String> inputCurrentLocation(String[] geoLocations) {
Map<String, Object> data = new HashMap<>();
data.put( "lat", geoLocations[0] );
data.put( "long", geoLocations[1] );
return mFunctions
.getHttpsCallable( "findNearestBranch" )
.call( data )
.continueWith( new Continuation<HttpsCallableResult, String>() {
#Override
public String then(#NonNull Task<HttpsCallableResult> task) throws Exception {
String result = (String) task.getResult().getData();
return result;
}
} ).addOnCompleteListener( new OnCompleteListener<String>() {
#Override
public void onComplete(#NonNull Task<String> task) {
String result = task.getResult();
System.out.println("RESULT FROM NODE "+result+" SUCCESS"+task.isSuccessful());
}
} );
}
I have wasted countless hours online trying to find what is wrong with no success. Someone please point where my problem is.
You're not returning anything in your https callable.
Try adding a return to the calculateNearestBranch function and have that function return the result variable.
Also you should put some logging into the catch statements so your callable won't fail silently.
exports.findNearestBranch = functions.https.onCall((data, context) => {
var latitutde = data.lat;
var longitude = data.long;
return getLocationObject(latitutde,longitude)
.then(function(result){
var fromObject=result;
console.log('CONTEXT CLIENT '+latitutde+' LONG '+longitude);
// Added `return`
return calculateNearestBranch(fromObject)
.then(function(result){
console.log("TO APP "+JSON.stringify(result));
// Returned `result`
return result;
})
.catch(function(error){
});
})
.catch(function(error){
});
});
How to handle error response with Retrofit 2 using synchronous request?
I need process response that in normal case return pets array and if request has bad parametrs return error json object. How can I process this two situations?
I am trying to use this tutorial but the main problem is mapping normal and error json to objects.
My normal response example:
[ {
"type" : "cat",
"color": "black"
},
{
"type" : "cat",
"color": "white"
} ]
Error response example:
{"error" = "-1", error_description = "Low version"}
What I got:
Call<List<Pet>> call = getApiService().getPet(1);
Response<List<Pet>> response;
List<Pet> result = null;
try {
response = call.execute(); //line with exception "Expected BEGIN_ARRAY but was BEGIN_OBJECT at line 1 column 2 path"
if(!response.isSuccessful()){
Error error = parseError(response);
Log.d("error message", error.getErrorDescription());
}
if (response.code() == 200) {
result = response.body();
}
} catch (IOException e) {
e.printStackTrace();
}
Retrofit 2 has a different concept of handling "successful" requests than Retrofit 1. In Retrofit 2, all requests that can be executed (sent to the API) and for which you’re receiving a response are seen as "successful". That means, for these requests, the onResponse callback is fired and you need to manually check whether the request is actually successful (status 200-299) or erroneous (status 400-599).
If the request finished successfully, we can use the response object and do whatever we wanted. In case the error actually failed (remember, status 400-599), we want to show the user appropriate information about the issue.
For more details refer this link
After going through a number of solutions. Am posting it for more dynamic use. Hope this will help you guys.
My Error Response
{
"severity": 0,
"errorMessage": "Incorrect Credentials (Login ID or Passowrd)"
}
Below is as usual call method
private void makeLoginCall() {
loginCall = RetrofitSingleton.getAPI().sendLogin(loginjsonObject);
loginCall.enqueue(new Callback<Login>() {
#Override
public void onResponse(Call<Login> call, Response<Login> response) {
if (response != null && response.code() == 200){
//Success handling
}
else if (!response.isSuccessful()){
mServerResponseCode = response.code();
Util.Logd("In catch of login else " + response.message());
/*
* Below line send respnse to Util class which return a specific error string
* this error string is then sent back to main activity(Class responsible for fundtionality)
* */
mServerMessage = Util.parseError(response) ;
mLoginWebMutableData.postValue(null);
loginCall = null;
}
}
#Override
public void onFailure(Call<Login> call, Throwable t) {
Util.Logd("In catch of login " + t.getMessage());
mLoginWebMutableData.postValue(null);
mServerMessage = t.getMessage();
loginCall = null;
}
});
}
Below Is util class to handle parsing
public static String parseError(Response<?> response){
String errorMsg = null;
try {
JSONObject jObjError = new JSONObject(response.errorBody().string());
errorMsg = jObjError.getString("errorMessage");
Util.Logd(jObjError.getString("errorMessage"));
return errorMsg ;
} catch (Exception e) {
Util.Logd(e.getMessage());
}
return errorMsg;
}
Below in viewModel observer
private void observeLogin() {
loginViewModel.getmLoginVModelMutableData().observe(this, login -> {
if (loginViewModel.getSerResponseCode() != null) {
if (loginViewModel.getSerResponseCode().equals(Constants.OK)) {
if (login != null) {
//Your logic here
}
}
//getting parsed response message from client to handling class
else {
Util.stopProgress(this);
Snackbar snackbar = Snackbar.make(view, loginViewModel.getmServerVModelMessage(), BaseTransientBottomBar.LENGTH_INDEFINITE).setAction(android.R.string.ok, v -> { });
snackbar.show();
}
} else {
Util.stopProgress(this);
Snackbar snackbar = Snackbar.make(view, "Some Unknown Error occured", BaseTransientBottomBar.LENGTH_INDEFINITE).setAction(android.R.string.ok, v -> { });
snackbar.show();
}
});
}
I think you should create a generic response class (let's say GenericResponse), that is extended by a specific response class (let's say PetResponse). In the first one, you include generic attributes (error, error_description), and in the latter, you put your specific response data (List<Pet>).
In your case, I would go with something like this:
class GenericResponse {
int error;
String error_description;
}
class PetResponse extends GenericResponse {
List<Pet> data;
}
So, your successful response body should look like this:
{
"data": [ {
"type" : "cat",
"color": "black"
},
{
"type" : "cat",
"color": "white"
} ]
}
And your error response body should look like this:
{ "error" = "-1", error_description = "Low version"}
Finally, your response object that is returned from the API call should be:
Response<PetResponse> response;
Wrap all your calls in retrofit2.Response like so:
#POST("user/login")
suspend fun login(#Body form: Login): Response<LoginResponse>
A very simple retrofit 2 error handler:
fun <T> retrofitErrorHandler(res: Response<T>): T {
if (res.isSuccessful) {
return res.body()!!
} else {
val errMsg = res.errorBody()?.string()?.let {
JSONObject(it).getString("error") // or whatever your message is
} ?: run {
res.code().toString()
}
throw Exception(errMsg)
}
}
Then use them like:
try {
createdReport = retrofitErrorHandler(api...)
} catch (e: Exception) {
toastException(ctx = ctx, error = e)
}
I am trying to make sense of how to convert how they provide the example from the website to android/retrofit...
This is the code example for the website:
function get_signed_request(file){
var xhr = new XMLHttpRequest();
xhr.open("GET", "/sign_s3?file_name="+file.name+"&file_type="+file.type);
xhr.onreadystatechange = function(){
if(xhr.readyState === 4){
if(xhr.status === 200){
var response = JSON.parse(xhr.responseText);
upload_file(file, response.signed_request, response.url);
}
else{
alert("Could not get signed URL.");
}
}
};
xhr.send();
}
function upload_file(file, signed_request, url){
var xhr = new XMLHttpRequest();
xhr.open("PUT", signed_request);
xhr.setRequestHeader('x-amz-acl', 'public-read');
xhr.onload = function() {
if (xhr.status === 200) {
document.getElementById("preview").src = url;
document.getElementById("avatar_url").value = url;
}
};
xhr.onerror = function() {
alert("Could not upload file.");
};
xhr.send(file);
}
This is my android retrofit attempt:
#GET("/sign_s3")
public void getSign(
#Query("name") String userId,
#Query("type") String type,
Callback<UserResponse> callback);
#Multipart
#PUT("/{url}")
public void sendMedia(
#Path("url") String signRequest,
#Part("theNameToUse") String theNameToUse,
#Part("isItAPicture") boolean isItAPicture, //if true it is a picture
#Part("media") TypedFile media,
Callback<UserResponse> callback);
the User Response:
ApiManager.getAsyncApi().getSign(name, type, new Callback<UserResponse>() {
#Override
public void success(UserResponse userResponse, Response response) {
sendMedia(response.signed_request);
}
#Override
public void failure(RetrofitError error) {
throw error;
}
});
private sendApi(path) {
ApiManager.getAsyncApi().sendMedia(path, title, isPictureNotvideo, media, new Callback<UserResponse>() {
#Override
public void success(UserResponse userResponse, Response response) {
}
#Override
public void failure(RetrofitError error) {
throw error;
}
});
}
the link to the heroku document is: https://devcenter.heroku.com/articles/s3-upload-node
response.signed_request does not work and is not an option... if I do response.getBody() that is the only thing I can see or to get the headers.... not sure how to grab the signed request...