Tutorial: Use PayPal Adaptive Payments API (with Embedded Lightbox)

Posted on Wed 20 February 2013 in IT, programming

I am working on integrating my Post Pay Counter Wordpress plugin with PayPal, so that site administrators can pay their writers directly from their blog pages, without having to head to the PayPal website.

PayPal APIs have the usual shitty documentation we have grown accustomed to in the tech world, so I am sharing what I have come up with until now (which works pretty well, actually), scheduling a second part of the tutorial for when the job will be completed.

PayPal Adaptive Payments API: what it is for

Adaptive payments handles payments between a sender of a payment and one or more receivers of the payment. You are an application owner, such as a merchant that owns a website, the owner of a widget on a social networking site, the provider of a payment application on mobile phones, and so on. Your application is the caller of Adaptive Payments API operations.

Standing to what I have been able to discover about PayPal's different payment mechanisms over summer, Adaptive Payments represents the most flexible way to transfer funds from one account to another. Also, it seems the only method you can effectively integrate PayPal in your application. Adaptive Payments is in fact for those applications in which your account, as application developer, is not the one you are drawing funds from. Shortly, you need to move money on behalf of someone, and your application is the intermediary.

AdapPymntRolesApp_B]

In my specific case, I needed a way to let administrators put their credentials into my plugin and have PayPal let me get money from their accounts and transfer it to their writers' ones. Adaptive Payments method was really suitable because it allows six transactions per each request, so that it is possible to send different amounts to several people with only one API request. As bottom line (which I did not need and did not care to dive into), it also allows Chained Payments, in which the primary receiver passes part of the payment to other receivers, splitting the original amount.

We are almost beginning. Just let me give you the link to the complete, detailed, give-a-lot-for-granted PayPal official documentation. There is also a brilliant PHP class I have used (and that will be used in this tutorial) which is Angell EYE's PayPal Payments Pro PHP Class. Apart from Adaptive Payments, you can execute almost any API-related PHP task with that class.

Application Workflow

This is how the PayPal-related part of the application will work:

  1. A form will contain all the data we need to complete the transaction (i.e. amount(s) and receiver(s) address).
  2. The transaction must then be prepared. When the submit button of the form is clicked, Javascript collects all the data and an AJAX request is issued to a PHP background page that takes them in and arranges the PayPal API request. PayPal will return a Pay Key, that our PHP page will return as result of the AJAX results. At this point we are still on the first page, nothing seems to have happened.
  3. Having the PayPal Pay Key makes it possible to execute the transaction, finally. Thus, the user is shown the PayPal forms and confirmation stuff that let them complete the payment.

AdapPymntSrvcFlow_A]

Getting started with Adaptive Payments: the front-end

The first thing we are going to set up is the front end. There are two ways you can have it:

  1. In a new window. This is the simplest method, which you will get to work pretty easily. When the user clicks to execute the payment, a new window/tab is opened where the transaction is completed. User are then momentarily forced to leave your website, to which they will be automatically redirected when the payment is done.
  2. In a lightboxed iframe. Although this is a bit harder, it will make the user experience a lot better. Users never leave your website: the transaction happens on top of your page, while the content gets dark. This is what this tutorial walks you through.

First of all, we need two forms, either having its own submit button:

<form id="prepare_payment_form" action="#" method="post">
   <input id="prepare_payment" name="prepare_payment" type="submit" value="Prepare payment with PayPal" />
</form>
<form id="execute_payment_form" action="#" method="post" target="PPDGFrame">
   <input id="execute_payment" disabled="disabled" name="execute_payment" type="submit" value="Execute payment with PayPal" />
</form>

Note the target="PPDGFrame" for the second form. The execute_payment submit button is disabled because we do not want users to execute the payment before they have actually prepared it. I have the preparation handled by AJAX, so that no reload is needed. I guess that if you are smart and masochist enough to be willing to deal with PayPal APIs and read this tutorial, you will also know how to Javascript-enable the execute_payment button and the like stuff (I am lazy enough not to provide you with that code, sorry).

You may also want to add somewhere a space for errors to be displayed, that will be invisible until some error comes up.

The first form, apart from its submit button, will also contain the data needed to fulfill the PayPal request. I am really keeping this simple, putting two email addresses and two amounts directly as form data, but you can fetch your data the way you need (e.g. store in the form only the user ID and then pulling from some database the other data when the request will be prepared by PHP).

<form id="prepare_payment_form" action="#" method="post">
   <input name="payment_1" type="text" value="50" />
   <input name="user_address_1" type="text" value="my_first_address@domain.com" />
   <input name="payment_2" type="text" value="25" />
   <input name="user_address_2" type="text" value="my_second_address@domain.com" />
   <input id="prepare_payment" name="prepare_payment" type="submit" value="Prepare payment with PayPal" />
</form>

Now, when the form is submitted, we will need to grab those data and send them to a secondary PHP page that will handle the whole thing.

jQuery(document).ready(function($) {javascript
   $("#prepare_payment").unbind('click').click(function(e) {
   e.preventDefault();

So, from Javascript to English, when the prepare_payment button is clicked (row 3), the default action is prevented, so that the form is not sent and we allow AJAX to take over (row 4). As for the unbinding and subsequent binding, I have no clue why this happens, but it prevents the event from firing multiple times.

var data = {
   action: 'the_php_page_that_will_handle_the_request.php',
   payment_data: $('#prepare_payment_form').serialize()
};

Quite easy: preparing the AJAX request data, which is actually simply a serialization of the form data. If you need more stuff, you can add more indexes to the array. You may also want to add a confirmation step, which is what I did:

var agree = confirm('You are just about to ask the Post Pay Counter to prepare a PayPal payment on your behalf.');
if (!agree)
return false;

From the moment the payment is being prepared on the related button can not be clicked anymore. Moreover, to prevent changes to the form, all the text inputs are disabled:

$('#prepare_payment').attr("disabled", "true");
jQuery("#prepare_payment_form :text").each(function() {
   jQuery(this).attr("disabled", "true");
});

Then we finally fire the AJAX request to get the Pay Key:

$.post(ajaxurl, data, function(response) {

   if(response.indexOf("Error: ") != -1) {
      $("#paypal_error").css("display", "block");
      $("#paypal_error").html(response);
      $('#prepare_payment').removeAttr("disabled");
      jQuery("#prepare_payment_form :text").each(function() {
         jQuery(this).removeAttr("disabled");
      });
      return false;
   }

   //Initialize PayPal embedded payment flow. Not loading it on document ready so that we only have it if user prepares payment, not just loads the page…
   var dgFlow = new PAYPAL.apps.DGFlow({trigger: 'execute_payment'});

   //Store PayKey in the form action and enable execute payment button
   $("#execute_payment_form").attr("action", "https://www.sandbox.paypal.com/webapps/adaptivepayment/flow/pay?expType=light&payKey=" + response);
   $('#execute_payment').removeAttr("disabled");
});

Inside the AJAX request, the first block is for error handling: if something bad happens the form button and inputs are clickable and editable again and the error is shown in the proper div.

If the request is successful, The PayPal flow is initialized, using as trigger parameter the HTML ID of the submit button of the second form, the execute_payment one. Finally, the action of the execute payment form is updated with the PayPal URL and the Pay Key received as results of the AJAX request, and the submit button is enabled, so that the payment can be executed. (When that will happen, PayPal libraries will take over and do the job).

There is one more thing to add to the document.ready of the page:

jQuery(document).ready(function($) {
   if (window != top) {
      top.location.replace(document.location);
   }

This will ensure that when/if the transaction is over, either because the user canceled it or because it was complete, the iframe gets closed automatically. I have not found a better way to handle this, so if you Javascript guys have some better ways, I do really long to get rid of that crappy redirect.

Getting started with PayPal Adaptive Payments: the back-end

That was the front-end. Not let's focus on the PHP page to which the AJAX request is issued.

<?php
include( 'angell_eye_class_dir/Pay.php' );

//Payment data is passed via AJAX serialized. We explode them by & (dividing fields from each other) and then for each of them we explode again, this time by = (separating fields names and fields values). I know it looks awful, but it is not that hard...
$receivers          = array();
$payment_data       = array();
$payment_data_temp  = explode( '&', $_POST['payment_data'] );

foreach( $payment_data_temp as $single ) {
   $yet_another_temp   = explode( '=', $single );
   $userid             = substr( $yet_another_temp[0], -1 );

   $receivers[] = array(
      'Amount'           => $single['payment'.$userid],  // Required.  Amount to be paid to the receiver.
      'Email'            => $single['user_address'.$userid],  // Receiver's email address. 127 char max.
      'InvoiceID'        => '',  // The invoice number for the payment.  127 char max.
      'PaymentType'      => 'PERSONAL',  // Transaction type.  Values are:  GOODS, SERVICE, PERSONAL, CASHADVANCE, DIGITALGOODS
      'PaymentSubType'   => '',  // The transaction subtype for the payment.
      'Phone'            => array('CountryCode' => '', 'PhoneNumber' => '', 'Extension' => ''),  // Receiver's phone number.   Numbers only.
      'Primary'          => ''  // Whether this receiver is the primary receiver.  Values are boolean:  TRUE, FALSE
   );
}

$paypal_result = execute_payment( TRUE, $paypal_API_email, $paypal_API_password, $paypal_API_signature, $paypal_currency_code, 'SENDER', $receivers, $_POST['current_page'] );

if( $paypal_result['Ack'] == 'Failure' )
   die( 'Error: '.$paypal_result['Errors'][0]['Message'] );

die($paypal_result['PayKey']);

An array containing all the payment receivers (up to 6) is created, required fields are only Amount, Email and PaymentType. At this point, the only thing missing is the execute_payment function, which has the following parameters:

  • Whether you want to work in the PayPal sandbox for testing (TRUE) or not (FALSE);
  • The PayPal API email address of the account you are drawing funds from. For example, in my plugin it is the PayPal API email of the blog administrator;
  • The PayPal API password of the account you are drawing funds from;
  • The PayPal API signature of the account you are drawing funds from;
  • The code for the currency you want to use for payments, as EUR, USD, GBP...
  • Who will pay for PayPal fees, SENDER or RECEIVER;
  • The page you want the user to be redirected when the Adaptive Payment will be done (or canceled).

That function consists in a customized version of the Angel EYE's class Pay.php file as follows (I am stripping it of all the inline comments):

<?php
include_once('paypal.class.php');

function execute_payment( $sandbox, $api_username, $api_password, $api_signature, $currency, $fees_payer, $receivers, $return_page ) {

    // Create PayPal object.
    $PayPalConfig = array(
       'Sandbox' => $sandbox,
       'DeveloperAccountEmail' => '',
       'ApplicationID' =>
       'APP-80W284485P519543T',
       'DeviceID' => '',
       'IPAddress' => $_SERVER['REMOTE_ADDR'],
       'APIUsername' => $api_username,
       'APIPassword' => $api_password,
       'APISignature' => $api_signature,
       'APISubject' => ''
    );

    $PayPal = new PayPal_Adaptive($PayPalConfig);

    // Prepare request arrays
    $PayRequestFields = array(
       'ActionType' => 'CREATE',
       'CancelURL' => $return_page,
       'CurrencyCode' => $currency,
       'FeesPayer' => $fees_payer,
       'IPNNotificationURL' => '',
       'Memo' => '',
       'Pin' => '',
       'PreapprovalKey' => '',
       'ReturnURL' => $current_page,
       'ReverseAllParallelPaymentsOnError' => '',
       'SenderEmail' => '',
       'TrackingID' => ''
    );

    $ClientDetailsFields = array(
       'CustomerID' => '',
       'CustomerType' => '',
       'GeoLocation' => '',
       'Model' => '',
       'PartnerName' => 'Always Give Back'
    );

    $FundingTypes = array('ECHECK', 'BALANCE', 'CREDITCARD');
    $SenderIdentifierFields = array( 'UseCredentials' => '' );
    $AccountIdentifierFields = array(
       'Email' => '',
       'Phone' => array('CountryCode' => '', 'PhoneNumber' => '', 'Extension' => '')
    );

    $PayPalRequestData = array( 'PayRequestFields' => $PayRequestFields, 'ClientDetailsFields' => $ClientDetailsFields, 'FundingTypes' => $FundingTypes, 'Receivers' => $receivers, 'SenderIdentifierFields' => $SenderIdentifierFields, 'AccountIdentifierFields' => $AccountIdentifierFields );
    $PayPalResult = $PayPal->Pay($PayPalRequestData);

    return $PayPalResult;
}

It will probably need some tweaking on your part, but I have hopefully explained how the hell PayPal Adaptive Payments are supposed to work!