The Pay and PaymentDetails APIs

Before demonstrating an Adaptive Payments integration with the Tweet Relevance sample code from Appendix A, let’s take a closer look at the Adaptive Payments Pay[2] and PaymentDetails[3] APIs, which are integral to our implementation details. A complete and more comprehensive list of all of the Adaptive Payments API operations can be found at Adaptive Payments API documentation on X.com. This section focuses on two of the most essential APIs and relevant options for Tweet Relevance payment integration along with some sample code to quickly get you up and running.

If you come from an Express Checkout background or have been following along in the book chapter by chapter, an important distinction to make up front between Adaptive Payments and Express Checkout is the nature of the request parameters. Whereas Express Checkout involves sending in 3-Token credentials along with Name-Value pairs through the request body, Adaptive Payments require 3-Token credentials along with a mandatory application identifier and additional configuration information to be passed in as headers; the POST request payload identifies the API operation and its parameters. Let’s now turn to the Pay API and execute some sample API calls to see how it all works.

All payments made via the Pay API have the same essential fields and are outlined in Table 4-1. If thinking about Adaptive Payments from an Express Checkout mindset, you might consider the Pay API to be similar to the SetExpressCheckout API in that it sets up a transaction and returns a value called a “pay key” that can be used to redirect a sender to PayPal for approval.

Table 4-1. Common fields for the Pay API encoded in NVP format

FieldDescription
actionTypeWill be one of three possible values:
  • PAY: Use this value to set up a payment transaction except when using the request in combination with ExecutePaymentRequest.

  • CREATE: Used to set up payment instructions with a SetPaymentOptions request and then execute at a later time with an ExecutePaymentRequest.

  • PAY_PRIMARY: Used for chained payment situations only. This allows you to delay payments to secondary receivers at the time of the transaction and process only the primary receiver. To process the secondary payments, initiate ExecutePaymentRequest and pass the pay key obtained from the PayResponse.

receiverList .receiver( n ).email One or more receivers’ email addresses, where n can take on values between 0 and 5. For parallel payments, up to 6 receivers may be identified, and for chained payments, 1 primary receiver and 5 secondary receivers may be identified.
receiverList .receiver( n ) .amount The amount to be credited to each receiver’s account.
receiverList.receiver( n ).primary(Optional) Set this value to true to indicate that this is a chained payment. Only one receiver can be the primary receiver.
currencyCodeThe code for the currency in which the payment is made. You can specify only one currency, regardless of the number of receivers. A complete list of supported currency codes is available online.
cancelUrlThe URL for sender redirection if the sender cancels the payment approval. This value is required, but used only for explicit payments.
returnUrlThe URL for sender redirection after completion of the payment. This value is required, but used only for explicit payments.
requestEnvelope.errorLanguageThe requestEnvelope is required information common to each API operation and includes members such as errorLanguage, the language in which error messages are displayed, and the level of detail that should be returned for error messages. At the current time, the only supported error language is US English (en_US).
feesPayer(Optional) The payer of PayPal fees. Allowable values are:
  • SENDER: Sender pays all fees (for personal, implicit simple/parallel payments; do not use for chained or unilateral payments)

  • PRIMARYRECEIVER: Primary receiver pays all fees (chained payments only)

  • EACHRECEIVER: Each receiver pays his own fee (default, personal, and unilateral payments)

  • SECONDARYONLY: Secondary receivers pay all fees (use only for chained payments with one secondary receiver)

It’s a fine detail, but do note that part of the request includes a mandatory “request envelope.” It’s a subtle but important point that the existence of a dot separator in field names for the Adaptive Payments APIs indicates a notion of hierarchy. For example, requestEnvelope.errorLanguage connotes that there’s a requestEnvelope field with a sub-field errorLanguage. As you’ll see later in this chapter, a JSON object expressing this same field would be {'requestEnvelope' : {'errorLanguage' : 'en_US'}}. Additional parameters are possible to include as part of the request envelope and are specific to particular Adaptive Payments API operations and indicated in the more comprehensive online documentation.

For readers familiar with a Linux or Unix shell, a trivial Bash script that uses the curl command to execute a request might look like Example 4-1. Readers unfamiliar with Bash or command-line utilities should simply focus on the structure of the curl command that is being executed. A brief explanation follows, and subsequent examples for this chapter are written in Python as GAE web applications, so there’s no need to fret if learning Bash syntax wasn’t part of your expectations for this chapter.

In short, the script sets up a few variables, executes a curl command using those variables along with some other parameters, and displays the results. Although it’s just a trivial script, there’s a lot that can be gleaned. The following observations may be helpful in solidifying your understanding of how an Adaptive Payments Pay API operation takes place:

A sample response from executing the previous Pay API operation follows:

{ "payKey" : "AP-54G358058T2731358",
  "paymentExecStatus" : "CREATED",
  "responseEnvelope" : { "ack" : "Success",
      "build" : "2428464",
      "correlationId" : "7ca7e3aa6a999",
      "timestamp" : "2012-01-14T15:36:31.515-08:00"
    }
}

In short, the response is formatted as JSON as requested by the X-PAYPAL-RESPONSE-DATA-FORMAT header, a response envelope returns an acknowledgment that the request is successful, and the response indicates that a payment request has been created and includes a payKey value that can be used to redirect a sender to PayPal for approval. To initiate the approval process for a Sandbox application, an application must redirect the sender back to https://www.sandbox.paypal.com/cgi-bin/webscr?cmd=_ap-payment&paykey=value.

Note

Although not germane to the Tweet Relevance integration, it’s worthwhile to note that if the API caller and the sender are one and the same (an Implicit Payment), a senderEmail field can be specified, and PayPal will implicitly approve the payment without redirecting to PayPal for explicit approval. You can also use a preapproval to execute the payment and avoid explicit approval. The required preapproval fields include a preapproval key and personal identification number (PIN).

The PaymentDetails API is used to obtain information about a payment. You can identify the payment by your tracking ID, the PayPal transaction ID in an IPN message, or the pay key associated with the payment. Table 4-2 summarizes the common request parameters.

In short, you pass in one of several possible values that identifies a payment to PaymentDetails, and it returns relevant status information about the payment. Example 4-2 illustrates a trivial Bash script that makes a PaymentDetails API request using a payKey value returned from Example 4-1. Example usage for the script is to simply pass in the pay key as a command-line parameter to the script.

Sample results from the script follow and illustrate the basic format of a PaymentDetails response:

{ "actionType" : "PAY",
  "cancelUrl" : "http://example.com/cancel",
  "currencyCode" : "USD",
  "feesPayer" : "EACHRECEIVER",
  "ipnNotificationUrl" : "http://example.com/ipn",
  "memo" : "Simple payment example.",
  "payKey" : "AP-4U527241GF1114245",
  "paymentInfoList" : { "paymentInfo" : [ { "pendingRefund" : "false",
            "receiver" : { "amount" : "1.00",
                "email" : "XXX",
                "paymentType" : "SERVICE",
                "primary" : "false"
              }
          } ] },
  "responseEnvelope" : { "ack" : "Success",
      "build" : "2428464",
      "correlationId" : "4808cadb5297e",
      "timestamp" : "2012-01-14T17:58:11.358-08:00"
    },
  "returnUrl" : "http://example.com/return",
  "reverseAllParallelPaymentsOnError" : "false",
  "sender" : { "useCredentials" : "false" },
  "status" : "CREATED"
}

Of particular interest in the response for PaymentDetails is the status field that indicates that the payment request has been created but not yet completed; however, should you visit https://www.sandbox.paypal.com/cgi-bin/webscr?cmd=_ap-payment&paykey=AP-4U527241GF1114245 and successfully approve the payment, invoking PaymentDetails again should return a status of COMPLETED. However, a status of COMPLETED does not necessarily mean that the payment was successfully processed and that payment was rendered—it only means that the request, regardless of its ultimate outcome—was completed successfully. If the status were COMPLETED, additional information would be included regarding the specific details as they relate to the payment(s). For example, the following sample results show a PaymentDetails response where status is COMPLETED and the paymentInfoList field provides definitive information about the ultimate outcome of the payment. (In the case of an eCheck payment, the transactionStatus would have been PENDING.)

{
  "status": "COMPLETED", 
  "responseEnvelope": {
    "ack": "Success", 
    "timestamp": "2012-01-31T22:47:32.121-08:00", 
    "build": "2486531", 
    "correlationId": "e28c831c96f87"
  }, 
  "returnUrl": "http://example.com", 
  "payKey": "AP-72S344750E3616459", 
  "senderEmail": "XXX", 
  "actionType": "PAY", 
  "sender": {
    "email": "matthe_1325995267_per@zaffra.com", 
    "useCredentials": "false"
  }, 
  "paymentInfoList": {
    "paymentInfo": [
      {
        "refundedAmount": "0.00", 
        "receiver": {
          "paymentType": "SERVICE", 
          "amount": "9.99", 
          "email": "XXX", 
          "primary": "false"
        }, 
        "transactionId": "2NB983427X665902U", 
        "senderTransactionStatus": "COMPLETED", 
        "senderTransactionId": "11411689C90721011", 
        "pendingRefund": "false", 
        "transactionStatus": "COMPLETED"
      }
    ]
  }, 
  "currencyCode": "USD", 
  "cancelUrl": "http://example.com/cancel", 
  "feesPayer": "EACHRECEIVER", 
  "reverseAllParallelPaymentsOnError": "false"
}

If you’re comfortable working in a Linux or Unix environment or can comfortably execute curl commands in a Windows environment, it’s worthwhile to try manually executing these scripts to ensure that you understand the fundamentals. Regardless, in the next section, we’ll implement the same logic as a GAE project.

Let’s now take the concepts concerning Pay and PaymentDetails API requests from the previous sections and consolidate them into an austere GAE application. If you’ve been following along closely, Example 4-3 should seem fairly straightforward. It’s a web app that processes requests for two URLs: 1) a request on the root context of the application that triggers a Pay request and displays the response, and 2) a /status request that executes a PaymentDetails request for the original Pay request and displays the response. Since native Python list and dictionary objects are so close to a JSON representation, it makes sense to use JSON as both the request and response format, so you’ll notice a new import statement that makes available a JSON module for easily converting between the JSON string representation and the native Python objects. You’ll also see an import statement that brings in the memcache module that’s used to minimally mimic a session implementation, which the app uses to store and later look up the payKey returned from the Pay request and pass it through to the PaymentDetails request that is executed when /status is requested. Go ahead and take a look at the code; afterward, a play-by-play synopsis is provided that breaks down the action as a series of coarsely grained steps.

Example 4-3. A sample GAE application that executes a Simple Adaptive Payment—main.py

#!/usr/bin/env python

"""
A minimal GAE application that makes an Adaptive API request to PayPal
and parses the result. Fill in your own 3 Token Credentials and sample
account information from your own sandbox account
"""

import random

from google.appengine.ext import webapp
from google.appengine.ext.webapp import util
from google.appengine.api import urlfetch
from google.appengine.api import memcache
from django.utils import simplejson as json


# Replace these values with your own 3-Token credentials and a sample "seller"
# who is the receiver of funds to run this sample code in the developer sandbox

user_id = "XXX"
password = "XXX"
signature = "XXX"
receiver = "XXX"

class MainHandler(webapp.RequestHandler):

    # Helper function to execute requests with appropriate headers
    def _request(self, url, params):

        # standard Adaptive Payments headers
        headers = {
            'X-PAYPAL-SECURITY-USERID' : user_id,
            'X-PAYPAL-SECURITY-PASSWORD' : password,
            'X-PAYPAL-SECURITY-SIGNATURE' : signature,
            'X-PAYPAL-REQUEST-DATA-FORMAT' : 'JSON',
            'X-PAYPAL-RESPONSE-DATA-FORMAT' : 'JSON',
            'X-PAYPAL-APPLICATION-ID' : 'APP-80W284485P519543T'
        }
 
        return urlfetch.fetch(
                url,  
                payload = json.dumps(params),
                method=urlfetch.POST,
                validate_certificate=False,
                headers=headers
               )

    def get(self, mode=""):

        # /status - executes PaymentDetails when PayPal redirects back to this app after payment approval

        if mode == "status":

            payKey = memcache.get(self.request.get('sid'))

            params = {
                'requestEnvelope' : {'errorLanguage' : 'en_US', 'detailLevel' : 'ReturnAll'},
                'payKey' : payKey
            }

            result = self._request('https://svcs.sandbox.paypal.com/AdaptivePayments/PaymentDetails', params)

            response = json.loads(result.content)

            if result.status_code == 200: # OK

                # Convert back to indented JSON and display it

                pretty_json = json.dumps(response,indent=2)
                self.response.out.write('<pre>%s</pre>' % (pretty_json,))
            else:
                self.response.out.write('<pre>%s</pre>' % (json.dumps(response,indent=2),))

        else: # / (application root) - executed when app loads and initiates a Pay request

            amount = 10.00

            # A cheap session implementation that's leveraged in order to lookup the payKey
            # from the Pay API and execute PaymentDetails when PayPal redirects back to /status

            sid = str(random.random())[5:] + str(random.random())[5:] + str(random.random())[5:]

            return_url = self.request.host_url + "/status" + "?sid=" + sid
            cancel_url = return_url

            redirect_url = "https://www.sandbox.paypal.com/cgi-bin/webscr?cmd=_ap-payment&paykey="

            params = {
                    'requestEnvelope' : {'errorLanguage' : 'en_US', 'detailLevel' : 'ReturnAll'},
                    'actionType' : 'PAY',
                    'receiverList' : {
                            'receiver' : [
                                {'email' : receiver, 'amount' : amount}
                            ],
                    },
                'currencyCode' : 'USD',
                'memo' : 'Simple payment example.',
                'cancelUrl' : cancel_url,
                'returnUrl' : return_url,
            }

            result = self._request('https://svcs.sandbox.paypal.com/AdaptivePayments/Pay', params)
           
            response = json.loads(result.content)

            if result.status_code == 200: # OK

                # Convert back to indented JSON and inject a hyperlink to kick off payment approval

                pretty_json = json.dumps(response,indent=2)
                pretty_json = pretty_json.replace(response['payKey'], '<a href="%s%s" target="_blank">%s</a>' % (redirect_url, response['payKey'], response['payKey'],))
                memcache.set(sid, response['payKey'], time=60*10) # seconds

                self.response.out.write('<pre>%s</pre>' % (pretty_json,))
            else:
                self.response.out.write('<pre>%s</pre>' % (json.dumps(response,indent=2),))

def main():
    application = webapp.WSGIApplication([('/', MainHandler),
                                          ('/(status)', MainHandler)],
                                         debug=True)
    util.run_wsgi_app(application)

if __name__ == '__main__':
    main()

In terms of the overall application flow, here’s how it all breaks down:

Another key point to take away from the application is that the owner of the application need not necessarily be the receiver of the payment. It could certainly be the case that the same owner of the application whose 3-Token credentials are supplied to run the application could also be on the receiving end, but it could just as easily be the case that the owner of the application is a third party who built the application as a fixed-price contract job and maintains the application on behalf of the receiver as part of a business arrangement. However, it’s just as easily the case that perhaps the third party developer could have developed the application for free or at a deep discount in exchange for a cut of the payment. The next section illustrates how a chained payment could be used to accommodate exactly this kind of situation.

If you understand the flow of Example 4-3, the good news is that executing a chained (or parallel payment) literally just requires a couple of additional lines of code. Recall that in a chained payment scenario, the sender perceives that a payment is being sent to a primary receiver; however, the primary receiver essentially acts as a “middle man” who takes a cut and passes on potentially variable portions of the payment to up to five additional receivers. A realistic scenario involving a chained payment could be that the developer of an application takes a cut of a payment and passes on the remaining portion to additional parties such as investors who may be stakeholders in the business venture. While a parallel payment could conceivably be used to get the money into the very same hands, a chained payment allows the additional receivers to remain anonymous so far as the sender is concerned. From the sender’s point of view, there is only a single receiver.

As just mentioned, the changes to Example 4-3 that result in a chained payment are absolutely minimal. Instead of a single receiver and amount being specified and passed into the Pay request, multiple receivers can be passed. For example, consider the following receiver configuration:

params = { 
        'requestEnvelope' : {'errorLanguage' : 'en_US', 'detailLevel' : 'ReturnAll'},
        'actionType' : 'PAY',
        'receiverList' : { 
                'receiver' : [ 
                    {'email' : receiver1, 'amount' : amount1, 'primary' : True },
                    {'email' : receiver2, 'amount' : amount2, 'primary' : False},
                    {'email' : receiver3, 'amount' : amount2, 'primary' : False}
                ],  
        },  
    'currencyCode' : 'USD',
    'memo' : 'Chained payment example.',
    'cancelUrl' : cancel_url,
    'returnUrl' : return_url,
}

This configuration specifies that there is one primary receiver and two secondary receivers. If amount1 were $10.00, amount2 were $5.00, and amount3 were $2.00, the primary receiver would be accepting a $10.00 payment but passing on $7.00 of it to secondary receivers—effectively taking a $3.00 cut. An important detail to also note is that because no value for feesPayer is specified to override the default value of EACHRECEIVER, all receivers, including the primary receiver, pay the fees for this transaction.

Modifications to Example 4-3 that result in parallel payment are quite similar to those for a chained payment except that there is no designated primary receiver and the party who was the primary receiver takes an explicit cut of the payment in the parameters. Using the same configuration parameters as with the chained payment, the only code change required is that receiver1 no longer be designated as the primary receiver. However, in order for the same payment amounts to go to the receivers in the same manner as the chained payment scenario, amount1 would be an explicit $3.00 instead of $10.00. From the sender’s point of view, there are three receivers involved with the parallel payment, and the sender has visibility into how much of the payment is given to each of the receivers.

params = { 
        'requestEnvelope' : {'errorLanguage' : 'en_US', 'detailLevel' : 'ReturnAll'},
        'actionType' : 'PAY',
        'receiverList' : { 
                'receiver' : [ 
                    {'email' : receiver1, 'amount' : amount1, 'primary' : False},
                    {'email' : receiver2, 'amount' : amount2, 'primary' : False},
                    {'email' : receiver3, 'amount' : amount2, 'primary' : False}
                ],  
        },  
    'currencyCode' : 'USD',
    'memo' : 'Parallel payment example.',
    'cancelUrl' : cancel_url,
    'returnUrl' : return_url,
}

As with the prior examples, all receivers pay their own portion of the fees since no value for feesPayer has been provided to override the default value of EACHRECEIVER.