javascript – PayPal Payouts Integration with Firebase Features in iOS App

javascript – PayPal Payouts Integration with Firebase Features in iOS App


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:

  1. Is there something unsuitable with my request payload or the best way I name the Cloud Perform?
  2. Are there any recognized points or higher practices when utilizing GTMSessionFetcher for this sort of request?
  3. What could possibly be the doable causes for this Sudden error occurred message?

Any assist or options can be tremendously appreciated!

Leave a Reply

Your email address will not be published. Required fields are marked *