I am engaged on an iOS app that requires paying out winnings to gamers utilizing PayPal. I am integrating PayPal payouts by way of Firebase Cloud Features, however I hold encountering errors.
Here is the shopper aspect code:
I am utilizing GTMSessionFetcher to name the Firebase Cloud Perform for PayPal payouts.
import UIKit
import GTMSessionFetcherCore
import FirebaseAuth
import FirebaseFirestore
import FirebaseFunctions
class WinningsViewController: UIViewController {
var balanceLabel: UILabel!
var withdrawPaypalButton: UIButton!
var withdrawVenmoButton: UIButton!
var balanceListener: ListenerRegistration?
lazy var features = Features.features()
var currentFetcher: GTMSessionFetcher?
override func viewDidLoad() {
tremendous.viewDidLoad()
setupUI()
setupBalanceListener()
}
deinit {
balanceListener?.take away()
}
func setupUI() {
// UI setup code...
}
func setupBalanceListener() {
guard let consumer = Auth.auth().currentUser else { return }
let userUID = consumer.uid
let db = Firestore.firestore()
balanceListener = db.assortment("playerBalances").doc(userUID).addSnapshotListener { [weak self] (doc, error) in
if let doc = doc, doc.exists, let stability = doc.knowledge()?["balance"] as? Double {
self?.balanceLabel.textual content = String(format: "Steadiness: $%.2f", stability)
self?.updateButtonState(stability: stability)
}
}
}
func updateButtonState(stability: Double) {
let isEnabled = stability > 0.0
withdrawPaypalButton.isEnabled = isEnabled
withdrawVenmoButton.isEnabled = isEnabled
}
@objc func withdrawPaypalButtonTapped() {
let alertController = UIAlertController(title: "Withdraw with PayPal", message: "Please enter your PayPal e mail handle", preferredStyle: .alert)
alertController.addTextField { textField in
textField.placeholder = "PayPal e mail"
}
let withdrawAction = UIAlertAction(title: "Withdraw", fashion: .default) { [weak self] _ in
guard let e mail = alertController.textFields?.first?.textual content, !e mail.isEmpty else { return }
self?.initiatePaypalPayout(recipientEmail: e mail)
}
alertController.addAction(withdrawAction)
alertController.addAction(UIAlertAction(title: "Cancel", fashion: .cancel, handler: nil))
current(alertController, animated: true, completion: nil)
}
func initiatePaypalPayout(recipientEmail: String) {
guard let consumer = Auth.auth().currentUser else { return }
currentFetcher?.stopFetching()
let db = Firestore.firestore()
let userUID = consumer.uid
db.assortment("playerBalances").doc(userUID).getDocument { [weak self] (doc, error) in
if let doc = doc, doc.exists, let stability = doc.knowledge()?["balance"] as? Double {
guard let url = URL(string: "https://us-central1-your-project-id.cloudfunctions.internet/withdrawPayPal") else { return }
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.setValue("software/json", forHTTPHeaderField: "Content material-Sort")
let payload: [String: Any] = ["data": ["recipientEmail": recipientEmail, "amount": balance]]
do {
request.httpBody = strive JSONSerialization.knowledge(withJSONObject: payload, choices: [])
} catch {
return
}
self?.currentFetcher = GTMSessionFetcher(request: request)
self?.currentFetcher?.beginFetch { knowledge, error in
self?.currentFetcher = nil
if let error = error as NSError? {
self?.showPaymentErrorAlert(error: error.localizedDescription)
} else if let knowledge = knowledge,
let consequence = strive? JSONSerialization.jsonObject(with: knowledge, choices: []) as? [String: Any],
let success = consequence["success"] as? Bool, success {
self?.showPaymentSuccessAlert()
} else {
self?.showPaymentErrorAlert(error: "Sudden error occurred.")
}
}
}
}
}
func showPaymentSuccessAlert() {
let alertController = UIAlertController(title: "Cost Profitable", message: "Your payout has been efficiently initiated!", preferredStyle: .alert)
alertController.addAction(UIAlertAction(title: "OK", fashion: .default, handler: nil))
current(alertController, animated: true, completion: nil)
}
func showPaymentErrorAlert(error: String) {
let alertController = UIAlertController(title: "Cost Error", message: "There was an error initiating your payout: (error)", preferredStyle: .alert)
alertController.addAction(UIAlertAction(title: "OK", fashion: .default, handler: nil))
current(alertController, animated: true, completion: nil)
}
}
Server Aspect:
index.js:
const features = require('firebase-functions');
const admin = require('firebase-admin');
const cors = require('cors')({ origin: true });
const { withdrawPayPal } = require('./paypalPayout');
admin.initializeApp();
const db = admin.firestore();
exports.withdrawPayPal = features.https.onCall(async (knowledge, context) => {
const response = {
statusCode: 200,
headers: {
"Entry-Management-Enable-Origin": "*",
"Entry-Management-Enable-Strategies": "GET, POST, OPTIONS, PUT, DELETE",
"Entry-Management-Enable-Headers": "Content material-Sort, Authorization"
},
physique: null
};
strive {
const consequence = await withdrawPayPal(knowledge, context);
response.physique = JSON.stringify(consequence);
return response;
} catch (error) {
response.statusCode = 500;
response.physique = JSON.stringify({ error: error.message });
return response;
}
});
paypalPayout.js:
const features = require('firebase-functions');
const admin = require('firebase-admin');
const paypal = require('@paypal/payouts-sdk');
const clientId = 'YOUR_PAYPAL_CLIENT_ID';
const clientSecret="YOUR_PAYPAL_CLIENT_SECRET";
const atmosphere = new paypal.core.SandboxEnvironment(clientId, clientSecret);
const shopper = new paypal.core.PayPalHttpClient(atmosphere);
const getPayPalAccessToken = async () => {
strive {
const response = await shopper.fetchAccessToken();
return response.consequence.access_token;
} catch (error) {
console.error('Error getting PayPal entry token:', error);
throw new features.https.HttpsError('unknown', 'Did not get PayPal entry token.');
}
};
const createPayout = async (recipientEmail, quantity) => {
const requestBody = {
"sender_batch_header": {
"sender_batch_id": `Payouts_${new Date().getTime()}`,
"email_subject": "You have got a payout!",
"email_message": "You have got obtained a payout! Thanks for utilizing our service!"
},
"objects": [{
"recipient_type": "EMAIL",
"amount": {
"value": amount.toFixed(2),
"currency": "USD"
},
"receiver": recipientEmail,
"note": "Thank you for your patronage!",
"sender_item_id": `Item_${new Date().getTime()}`
}]
};
let request = new paypal.payouts.PayoutsPostRequest();
request.requestBody(requestBody);
strive {
const response = await shopper.execute(request);
return response.consequence;
} catch (error) {
console.error("PayPal Payout Error:", error);
throw new features.https.HttpsError('unknown', 'Error creating payout', error);
}
};
exports.withdrawPayPal = features.https.onCall(async (knowledge, context) => {
if (!context.auth) {
throw new features.https.HttpsError('unauthenticated', 'The operate should be known as whereas authenticated.');
}
const { recipientEmail, quantity } = knowledge.knowledge; // Adjusted to retrieve from knowledge object
if (!recipientEmail || !quantity) {
throw new features.https.HttpsError('invalid-argument', 'The operate should be known as with two arguments: recipientEmail and quantity.');
}
const userId = context.auth.uid;
const db = admin.firestore();
const userBalanceRef = db.assortment('playerBalances').doc(userId);
const userBalanceDoc = await userBalanceRef.get();
if (!userBalanceDoc.exists) {
throw new features.https.HttpsError('not-found', 'Consumer stability not discovered');
}
const userBalance = parseFloat(userBalanceDoc.knowledge().stability);
const payoutAmount = parseFloat(quantity);
if (userBalance < payoutAmount) {
throw new features.https.HttpsError('failed-precondition', 'Inadequate stability');
}
strive {
const payoutResponse = await createPayout(recipientEmail, payoutAmount);
const batchStatus = payoutResponse.batch_header.batch_status;
const batchId = payoutResponse.batch_header.payout_batch_id;
if (batchStatus === 'PENDING' || batchStatus === 'SUCCESS') {
await userBalanceRef.replace({
stability: userBalance - payoutAmount
});
return { success: true, message: 'Payout processed efficiently!', batchId, batchStatus };
} else {
throw new features.https.HttpsError('unknown', 'Payout failed with standing: ' + batchStatus);
}
} catch (error) {
throw new features.https.HttpsError('unknown', 'Payout failed.', error.message);
}
});
Regardless of following this strategy, I am encountering the error: Sudden error occurred. when initiating the payout. The Cloud Perform logs point out points however don’t present a transparent decision.
Questions:
- Is there something unsuitable with my request payload or the best way I name the Cloud Perform?
- Are there any recognized points or higher practices when utilizing GTMSessionFetcher for this sort of request?
- What could possibly be the doable causes for this Sudden error occurred message?
Any assist or options can be tremendously appreciated!