Batch payouts

Based on the requirements of your business, it might be more efficient to authorise several payouts in one action rather than confirming each one individually. The provided instructions detail how to assemble a batch of multiple payouts, allowing for a collective review and single-step authorisation of the entire batch.

Create a batch payout

In MoneyMoov, batch payouts function by linking a batch payout ID to each payout when you select multiple payouts for authorisation. This batch ID, which is not a permanent entity, allows you to authorise all the selected payouts in a single action. Follow these steps to create a batch payout.

1. Retrieving all payouts awaiting authorisation

If your workflow involves handling multiple payouts at once, or if you have a system in place that requires payouts to be authorised, it might be more efficient for you to gather all the payouts that are pending authorisation for the current merchant. This can be done using the /payouts GET endpoint. This endpoint is capable of fetching a complete list of payouts. To specifically retrieve payouts that are awaiting authorisation, you can apply a status filter. By setting this filter to "PENDING_APPROVAL", the endpoint will return only those payouts that are currently in the state of awaiting authorisation. This approach streamlines the process, allowing you to focus on payouts that require immediate attention.

using System.Net.Http.Json;

string merchantID = "<YOUR_MERCHANT_ID>";

string statusFilter = "PENDING_APPROVAL";

int pageNumber = 1;

int pageSize = 10;

string payoutsAPIUrl = $"https://api-sandbox.nofrixion.com/api/v1/payouts?merchantID={merchantID}&statuses={statusFilter}&pageNumber={pageNumber}&pageSize={pageSize}";

var jwtToken = "<ACCESS_TOKEN>";

var client = new HttpClient();
client.DefaultRequestHeaders.Add("Accept", "application/json");
client.DefaultRequestHeaders.Add("Authorization", $"Bearer {jwtToken}");

// Note optionalurl paramaters for paging the payout list are exposed in the API
// - see https://api-sandbox.nofrixion.com/swagger/index.html for full details

try
{
    var response = await client.GetAsync(payoutsAPIUrl);
    if (response.IsSuccessStatusCode)
    {
        // returns a list of payouts
        var payoutsPage = await response.Content.ReadFromJsonAsync<PayoutsPage>();
        if (payoutsPage != null)
        {
            // Do something with response...
            Console.WriteLine($"Showing page {payoutsPage.pageNumber} of {payoutsPage.totalPages}.");
            foreach (var payout in payoutsPage.content)
            {
                Console.WriteLine($"ID: {payout.id}, Currency: {payout.currency}, Amount: {payout.amount:0.00}, Destination: {payout.destination}, Your reference: {payout.yourReference}, Status: {payout.status}");
            }
        }
        else
        {
            Console.WriteLine("No payouts returned.");
        }
    }
    else
    {
        // HTTP error codes will return a MoneyMoov API problem object
        Console.WriteLine(await response.Content.ReadFromJsonAsync<ApiProblem>());
    }
}
catch (Exception e)
{
    Console.WriteLine($"Error: {e.Message}");
}

// type definitions for returned data
record PayoutsPage(List<Payout> content, int pageNumber, int pageSize, int totalPages, int totalSize);
record AccountIdentifier(string type, string currency, string iban, string bic, string sortCode, string accountNumber, string bitcoinAddress);

record Counterparty(string name, AccountIdentifier Identifier);
record Payout(string id, string accountID, string merchantID, string currentUserID, string currentUserRole, 
    string approvePayoutUrl, string userID, string type, string description, string currency,
    decimal amount, string yourReference, Counterparty destination, string theirReference, string status);

record ApiProblem(string type, string title, int status, string detail);
const axios = require('axios');

// Your merchant ID
const merchantID = "<YOUR_MERCHANT_ID>";

// Status filter
const statusFilter = "PENDING_APPROVAL";

// Pagination parameters
const pageNumber = 1;
const pageSize = 10;

// Construct the payouts API URL
const payoutsAPIUrl = `https://api-sandbox.nofrixion.com/api/v1/payouts?merchantID=${merchantID}&statuses=${statusFilter}&pageNumber=${pageNumber}&pageSize=${pageSize}`;

// Access token
const jwtToken = "<ACCESS_TOKEN>";

// Function to perform the GET request
async function getPayouts() {
    try {
        const response = await axios.get(payoutsAPIUrl, {
            headers: {
                'Accept': 'application/json',
                'Authorization': `Bearer ${jwtToken}`
            }
        });

        if (response.status === 200) {
            // Returns a list of payouts
            const payoutsPage = response.data;
            if (payoutsPage && payoutsPage.content.length > 0) {
                // Do something with response...
                console.log(`Showing page ${payoutsPage.pageNumber} of ${payoutsPage.totalPages}.`);
                payoutsPage.content.forEach(payout => {
                    console.log(`ID: ${payout.id}, Currency: ${payout.currency}, Amount: ${payout.amount.toFixed(2)}, Destination: ${payout.destination}, Your reference: ${payout.yourReference}, Status: ${payout.status}`);
                });
            } else {
                console.log("No payouts returned.");
            }
        } else {
            // HTTP error codes will return a MoneyMoov API problem object
            console.error('Error:', response.status, response.data);
        }
    } catch (error) {
        console.error('Error:', error.response ? error.response.data : error.message);
    }
}

// Call the function to perform the GET request
getPayouts();

// Type definitions for returned data (used for reference, not needed in actual Node.js code)
// PayoutsPage: { content: [Payout], pageNumber, pageSize, totalPages, totalSize }
// AccountIdentifier: { type, currency, iban, bic, sortCode, accountNumber, bitcoinAddress }
// Counterparty: { name, Identifier }
// Payout: { id, accountID, merchantID, currentUserID, currentUserRole, approvePayoutUrl, userID, type, description, currency, amount, yourReference, destination, theirReference, status }
// ApiProblem: { type, title, status, detail }

import requests

# Your merchant ID
merchant_id = "<YOUR_MERCHANT_ID>"

# Status filter
status_filter = "PENDING_APPROVAL"

# Pagination parameters
page_number = 1
page_size = 10

# Construct the payouts API URL
payouts_api_url = f"https://api-sandbox.nofrixion.com/api/v1/payouts?merchantID={merchant_id}&statuses={status_filter}&pageNumber={page_number}&pageSize={page_size}"

# Access token
jwt_token = "<ACCESS_TOKEN>"

# Set up headers for the HTTP request
headers = {
    "Accept": "application/json",
    "Authorization": f"Bearer {jwt_token}"
}

# Function to perform the GET request
def get_payouts():
    try:
        response = requests.get(payouts_api_url, headers=headers)

        if response.status_code == 200:
            # Returns a list of payouts
            payouts_page = response.json()
            if payouts_page and payouts_page.get("content"):
                # Do something with response...
                print(f"Showing page {payouts_page['pageNumber']} of {payouts_page['totalPages']}.")
                for payout in payouts_page["content"]:
                    print(f"ID: {payout['id']}, Currency: {payout['currency']}, Amount: {payout['amount']:.2f}, Destination: {payout['destination']}, Your reference: {payout['yourReference']}, Status: {payout['status']}")
            else:
                print("No payouts returned.")
        else:
            # HTTP error codes will return a MoneyMoov API problem object
            print("Error:", response.status_code)
            print(response.json())
    except Exception as e:
        print(f"Error: {str(e)}")

# Call the function to perform the GET request
get_payouts()

# Type definitions for returned data (used for reference, not needed in actual Python code)
# PayoutsPage: { content: [Payout], pageNumber, pageSize, totalPages, totalSize }
# AccountIdentifier: { type, currency, iban, bic, sortCode, accountNumber, bitcoinAddress }
# Counterparty: { name, Identifier }
# Payout: { id, accountID, merchantID, currentUserID, currentUserRole, approvePayoutUrl, userID, type, description, currency, amount, yourReference, destination, theirReference, status }
# ApiProblem: { type, title, status, detail }

2. Select payouts to create a batch payout

Choose the payout IDs you obtained from step 1 that you wish to authorise together. To create a batch payout use the payouts/batch POST endpoint as shown below:

using System.Net.Http.Json;

var jwtToken = "<ACCESS_TOKEN>";

string batchPayoutsUrl = "https://api-sanbox.nofrixion.com/api/v1/payouts/batch";

var client = new HttpClient();
client.DefaultRequestHeaders.Add("Accept", "application/json");
client.DefaultRequestHeaders.Add("Authorization", $"Bearer {jwtToken}");

// Creating an anonymous object that will be serialized to JSON
var data = new List<string> { "<PAYOUT1_ID>", "PAYOUT2_ID","..." };

HttpResponseMessage response = await client.PostAsJsonAsync(batchPayoutsUrl, data);
if (response.IsSuccessStatusCode)
{
    Console.WriteLine(response.StatusCode);
    
    var batchPayout = await response.Content.ReadFromJsonAsync<BatchPayout>();
    Console.WriteLine($"Batch payout ID: {batchPayout.id}");
    Console.WriteLine("Payouts in batch:");
    foreach (var payout in batchPayout.payouts)
    {
        Console.WriteLine(payout);
    }
}
else
{
    // HTTP error codes will return a MoneyMoov API problem object
    Console.WriteLine(await response.Content.ReadFromJsonAsync<ApiProblem>());
}

// Type definitions for returned data
record BatchPayout(string id, List<Payout> payouts);
record AccountIdentifier(string type, string currency, string iban, string bic, string sortCode, string accountNumber, string bitcoinAddress);

record Counterparty(string name, AccountIdentifier Identifier);
record Payout(string id, string accountID, string merchantID, string currentUserID, string currentUserRole, 
    string approvePayoutUrl, string userID, string type, string description, string currency,
    decimal amount, string yourReference, Counterparty destination, string theirReference);

record ApiProblem(string type, string title, int status, string detail);
const axios = require('axios');

const jwtToken = "<ACCESS_TOKEN>";
const batchPayoutsUrl = "https://api-sanbox.nofrixion.com/api/v1/payouts/batch";

// Configure Axios HTTP client with default headers
const client = axios.create({
    headers: {
        'Accept': 'application/json',
        'Authorization': `Bearer ${jwtToken}`
    }
});

// Data to be sent (list of payout IDs)
const data = ["<PAYOUT1_ID>", "<PAYOUT2_ID>", "..."];

// Making a POST request to the API
client.post(batchPayoutsUrl, data)
    .then(response => {
        console.log(response.status);

        const batchPayout = response.data;
        console.log(`Batch payout ID: ${batchPayout.id}`);
        console.log("Payouts in batch:");
        batchPayout.payouts.forEach(payout => {
            console.log(payout);
        });
    })
    .catch(error => {
        // Handling errors (MoneyMoov API problem object)
        if (error.response) {
            console.log(error.response.data);
        } else {
            console.log(error.message);
        }
    });

// The structure of the expected response can be defined using classes or schemas
// For simplicity, it's handled as dynamic objects in this example

import requests
import json

# Your JWT token for authentication
jwt_token = "<ACCESS_TOKEN>"

# URL for the batch payouts endpoint
batch_payouts_url = "https://api-sanbox.nofrixion.com/api/v1/payouts/batch"

# Set up headers with token and content type
headers = {
    "Accept": "application/json",
    "Authorization": f"Bearer {jwt_token}"
}

# Creating a list of payout IDs that will be serialized to JSON
data = ["<PAYOUT1_ID>", "<PAYOUT2_ID>", "..."]

# Making the POST request
response = requests.post(batch_payouts_url, headers=headers, json=data)

# Handling the response
if response.status_code == 200:
    print(response.status_code)
    
    # Extracting batch payout information from the response
    batch_payout = response.json()
    print(f"Batch payout ID: {batch_payout['id']}")
    print("Payouts in batch:")
    for payout in batch_payout['payouts']:
        print(payout)
else:
    # Handling HTTP error codes
    print(response.json())

# Assuming response JSON structure for BatchPayout, Payout, and ApiProblem
# You might need to adjust these based on the actual response structure

This process will generate a unique batch payout ID, along with a list of all the payouts linked to this ID. To proceed with authorising these payouts, you will require this specific batch payout ID.

3. Authorise the batch payout

In MoneyMoov, a batch payout is a transaction that moves money from your account and requires secure authorisation through strong customer authentication. This process is crucial for ensuring the safety of your financial transactions. To authorise a batch payout and secure a strong access token, a specific procedure needs to be followed. Detailed instructions for this can be found in their documentation here.

4. Submit batch payout for processing

A batch payout can be submitted for processing through the payouts/batch/submit/{id} POST endpoint, as demonstrated below. Use the strong access token mentioned above.

using System.Net.Http.Json;

var batchPayoutIDToSubmit = "<BATCH_PAYOUT_ID_TO_SUBMIT>";

var jwtToken = "<STRONG_ACCESS_TOKEN>"; // Replace with your strong access token

string batchPayoutSubmitUrl = $"https://api-sandbox.nofrixion.com/api/v1/payouts/batch/submit/{batchPayoutIDToSubmit}";

var client = new HttpClient();
client.DefaultRequestHeaders.Add("Accept", "application/json");
client.DefaultRequestHeaders.Add("Authorization", $"Bearer {jwtToken}");

HttpResponseMessage response = await client.PostAsync(batchPayoutSubmitUrl, null);
if (response.IsSuccessStatusCode)
{
    // "Accepted" on success
    Console.WriteLine(response.StatusCode);
}
else
{
    // HTTP error codes will return a MoneyMoov API problem object
    Console.WriteLine(await response.Content.ReadFromJsonAsync<ApiProblem>());
}

// Type definitions for returned data
record ApiProblem(string type, string title, int status, string detail);
const axios = require('axios');

const batchPayoutIDToSubmit = "<BATCH_PAYOUT_ID_TO_SUBMIT>"; // Replace with your batch payout ID

const jwtToken = "<STRONG_ACCESS_TOKEN>"; // Replace with your strong access token

// Construct the URL for submitting the batch payout
const batchPayoutSubmitUrl = `https://api-sandbox.nofrixion.com/api/v1/payouts/batch/submit/${batchPayoutIDToSubmit}`;

// Set up the HTTP client headers
const config = {
    headers: {
        "Accept": "application/json",
        "Authorization": `Bearer ${jwtToken}`
    }
};

// Make the POST request
axios.post(batchPayoutSubmitUrl, null, config)
    .then(response => {
        if (response.status === 202) {
            // "Accepted" on success
            console.log("Accepted:", response.status);
        }
    })
    .catch(error => {
        if (error.response) {
            // HTTP error codes will return a MoneyMoov API problem object
            console.log("Error:", error.response.data);
        } else {
            console.log("Error:", error.message);
        }
    });

// Assuming an error response format (ApiProblem)
// Adjust according to the actual API response structure

import requests

# Replace with the actual batch payout ID to submit
batch_payout_id_to_submit = "<BATCH_PAYOUT_ID_TO_SUBMIT>"

# Replace with your strong access token
jwt_token = "<STRONG_ACCESS_TOKEN>"

# Construct the URL for submitting the batch payout
batch_payout_submit_url = f"https://api-sandbox.nofrixion.com/api/v1/payouts/batch/submit/{batch_payout_id_to_submit}"

# Set up the HTTP headers
headers = {
    "Accept": "application/json",
    "Authorization": f"Bearer {jwt_token}"
}

# Make the POST request
response = requests.post(batch_payout_submit_url, headers=headers)

# Check if the response status code is 202 (Accepted)
if response.status_code == 202:
    # "Accepted" on success
    print("Accepted:", response.status_code)
else:
    # HTTP error codes will return a MoneyMoov API problem object
    print("Error:", response.json())

# Assuming an error response format (ApiProblem)
# Adjust according to the actual API response structure