Paypal Payouts API Integration in PHP

In previous post we demonstrated how to implement paypal checkout experience using smart payment buttons. Paypal Payouts allows you to send payments to multiple recipients at the same time.

Paypal Payouts API Integration in PHP

With paypal payout you can send payments to multiple recipients as a batch. There are three types of payouts paypal provides.
  1. Large Batch Payout: Create a payouts file and upload it to secure FTP server. Number of recipients is unlimited.
  2. API Integration: Make programming calls to payout API. Number of recipients is up to 15000 per request.
  3. Payouts Web: Create a payouts file and upload it using Payouts Web. Number of recipients is 5000 per file.

We will be using API Integration to programmatically send payments to multiple receivers at a time. Before you can send payout requests to payouts API you must have:
  • A paypal account.
  • Client ID and Client Secret. 
  • Sufficient funds in your account.
  • Payout feature must be enabled. 

First setup a sandbox account and create an App. After your app is created get your client ID and client secret, next under "SANDBOX APP SETTINGS" enable payouts options to allow your app to send requests to Paypal Payouts API. You can follow instructions for creating an app and getting your client ID & Secret.

I have divided the payouts integration into three basic steps:
  1. A function to send CURL requests.
  2. A request to get access token.
  3. Final request to send payouts.

Before you send any kind of resource request to paypal you need an access token to authenticate your request. We will be sending two requests one to get an access token and other to send payouts so we will first write a function that can be used in both requests. To get an access token we need our client ID and Client Secret.

Contsants

define("PAYPAL_CLIENT_ID", "YOUR_CLIENT_ID");
define("PAYPAL_CLIENT_SECRET","YOUR_CLIENT_SECRET");
define("PAYPAL_TOKEN_URL", "https://api.sandbox.paypal.com/v1/oauth2/token");
define("PYPAL_PAYOUTS_URL", "https://api.sandbox.paypal.com/v1/payments/payouts");

Function to Send cURL Reuests

<?php
function curl_request($url, $method, $headers = [], $data = [], $curl_options = []){

    $curl = curl_init();

    curl_setopt($curl, CURLOPT_URL, $url);
    curl_setopt($curl, CURLOPT_TIMEOUT, 0);
    curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, false);
    curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false);

    //--- If any headers set add them to curl request
    if(!empty($headers)){
        curl_setopt($curl, CURLOPT_HTTPHEADER, $headers);
    }

    //--- Set the request type , GET, POST, PUT or DELETE
    switch($method){
        case "POST":
        curl_setopt($curl, CURLOPT_CUSTOMREQUEST, "POST");
        break;
    case "PUT":
        curl_setopt($curl, CURLOPT_CUSTOMREQUEST, "PUT");
        break;
    case "DELETE":
        curl_setopt($curl, CURLOPT_CUSTOMREQUEST, "DELETE");
        break;
    default:
        curl_setopt($curl, CURLOPT_CUSTOMREQUEST, "GET");
        break;
    }

    //--- If any data is supposed to be send along with request add it to curl request
    if($data){
        curl_setopt($curl, CURLOPT_POSTFIELDS, $data);
    }
    //--- Any extra curl options to add in curl object
    if($curl_options){
        foreach($curl_options as $option_key => $option_value){
            curl_setopt($curl, $option_key, $option_value);
        }
    }

    $response = curl_exec($curl);
    $error = curl_error($curl);
    curl_close($curl);

    //--- If curl request returned any error return the error
    if ($error) {
        return "CURL Error: $error";
    }
    //--- Return response received from call
    return $response;
}
?>

Get Access Token

<?php
//--- Headers for our token request
$headers[] = "Accept: application/json";
$headers[] = "Content-Type: application/x-www-form-urlencoded";

//--- Data field for our token request
$data = "grant_type=client_credentials";

//--- Pass client id & client secrent for authorization
$curl_options[CURLOPT_USERPWD] = PAYPAL_CLIENT_ID . ":" . PAYPAL_CLIENT_SECRET;

$token_request = curl_request(PAYPAL_TOKEN_URL, "POST", $headers, $data, $curl_options);
$token_request = json_decode($token_request);
if(isset($token_request->error)){
    die("Paypal Token Error: ". $token_request->error_description);
}
?>

Payouts Request

<?php
$headers = $data = [];
//--- Headers for payout request
$headers[] = "Content-Type: application/json";
$headers[] = "Authorization: Bearer $token_request->access_token";

$time = time();
//--- Prepare sender batch header
$sender_batch_header["sender_batch_id"] = $time;
$sender_batch_header["email_subject"]   = "Payout Received";
$sender_batch_header["email_message"]   = "You have received a payout, Thank you for using our services";

//--- First receiver
$receiver["recipient_type"] = "EMAIL";
$receiver["note"] = "Thank you for your services";
$receiver["sender_item_id"] = $time++;
$receiver["receiver"] = "[email protected]";
$receiver["amount"]["value"] = 10.00;
$receiver["amount"]["currency"] = "USD";
$items[] = $receiver;

//--- Second receiver
$receiver["recipient_type"] = "EMAIL";
$receiver["note"] = "You received a payout for your services";
$receiver["sender_item_id"] = $time++;
$receiver["receiver"] = "[email protected]";
$receiver["amount"]["value"] = 15.00;
$receiver["amount"]["currency"] = "USD";
$items[] = $receiver;

$data["sender_batch_header"] = $sender_batch_header;
$data["items"] = $items;

//--- Send payout request
$payout = curl_request(PYPAL_PAYOUTS_URL, "POST", $headers, json_encode($data));
?>

For demo purpose I used static array of receivers, you can prepare your receivers list from database table or file. Paypal prevents duplicate payouts if sender_batch_id has been already used in past 30 days paypal will reject the payout request and return an error. Below is the successful payout response.
{
  "batch_header": {
    "sender_batch_header": {
      "sender_batch_id": "2014021801",
      "email_subject": "You have a payout!"
    },
    "payout_batch_id": "12345678",
    "batch_status": "PENDING"
  }
}